Repository: nurjahan-shiah/Software-Design
Branch: main
Commit: 3ebfdefddd5d
Files: 64
Total size: 252.4 KB
Directory structure:
gitextract_3g0h0e_9/
├── D1/
│ └── Sequence Diagram/
│ ├── Sequence1.drawio
│ ├── Sequence2.drawio
│ └── sequence3.drawio
└── D2/
├── .vscode/
│ └── settings.json
├── data/
│ ├── bookings.csv
│ ├── equipment.csv
│ └── users.csv
├── lib/
│ └── javacsv.jar
└── src/
├── Main.java
├── data/
│ ├── .gitkeep
│ ├── BookingDAO.java
│ ├── EquipmentDAO.java
│ └── UserDAO.java
├── gui/
│ ├── .gitkeep
│ ├── CoordinatorDashboardPanel.java
│ ├── LoginPanel.java
│ ├── MainFrame.java
│ ├── ManagerDashboardPanel.java
│ ├── UI.java
│ └── UserDashboardPanel.java
├── model/
│ ├── .gitkeep
│ ├── Deposit.java
│ ├── Equipment.java
│ ├── Faculty.java
│ ├── Guest.java
│ ├── HeadLabCoordinator.java
│ ├── LabLocation.java
│ ├── LabManager.java
│ ├── PaymentTransaction.java
│ ├── Researcher.java
│ ├── Reservation.java
│ ├── Student.java
│ └── User.java
└── pattern/
├── command/
│ ├── .gitkeep
│ ├── CancelCommand.java
│ ├── Command.java
│ ├── ExtendCommand.java
│ ├── ForfeitDepositCommand.java
│ ├── ModifyCommand.java
│ ├── ReservationService.java
│ └── ReserveCommand.java
├── factory/
│ ├── .gitkeep
│ └── UserFactory.java
├── observer/
│ ├── .gitkeep
│ ├── EquipmentObserver.java
│ ├── EquipmentSubject.java
│ ├── Observer.java
│ └── UserNotificationObserver.java
├── singleton/
│ ├── .gitkeep
│ ├── BookingSystem.java
│ └── HeadLabCoordinatorSingleton.java
├── state/
│ ├── .gitkeep
│ ├── AvailableState.java
│ ├── DisabledState.java
│ ├── EquipmentContext.java
│ ├── EquipmentState.java
│ └── MaintenanceState.java
└── strategy/
├── .gitkeep
├── CreditCardPayment.java
├── DebitPayment.java
├── GrantPayment.java
├── InstitutionalPayment.java
├── PaymentProcessor.java
└── PaymentStrategy.java
================================================
FILE CONTENTS
================================================
================================================
FILE: D1/Sequence Diagram/Sequence1.drawio
================================================
================================================
FILE: D1/Sequence Diagram/Sequence2.drawio
================================================
================================================
FILE: D1/Sequence Diagram/sequence3.drawio
================================================
================================================
FILE: D2/.vscode/settings.json
================================================
{
"java.project.referencedLibraries": [
"lib/**/*.jar"
]
}
================================================
FILE: D2/data/bookings.csv
================================================
bookingID,userID,equipmentID,startTime,endTime,status,depositAmount,depositForfeited,paymentMethod,isExtended
B001,U001,EQ001,2026-03-20 09:00,2026-03-20 11:00,CONFIRMED,10.0,false,CREDIT,false
B002,U002,EQ004,2026-03-21 14:00,2026-03-21 16:00,CONFIRMED,15.0,false,INSTITUTIONAL,false
================================================
FILE: D2/data/equipment.csv
================================================
equipmentID,name,description,type,status,buildingName,roomNumber
EQ001,Oscilloscope,Digital oscilloscope 200MHz,Electronics,AVAILABLE,Lassonde,101
EQ002,3D Printer,FDM 3D printer,Fabrication,AVAILABLE,Bergeron,202
EQ003,Laser Cutter,CO2 laser cutter,Fabrication,MAINTENANCE,Lassonde,103
EQ004,Spectrometer,UV-Vis spectrometer,Optics,AVAILABLE,Petrie,305
EQ005,CNC Machine,3-axis CNC milling machine,Fabrication,DISABLED,Bergeron,204
================================================
FILE: D2/data/users.csv
================================================
userID,name,email,password,userType,staffID,status,department
U001,Alice Student,alice@yorku.ca,pass123,STUDENT,S12345,ACTIVE,Engineering
U002,Bob Faculty,bob@yorku.ca,pass123,FACULTY,F67890,ACTIVE,Science
U003,Carol Researcher,carol@yorku.ca,pass123,RESEARCHER,R11111,ACTIVE,Research
U004,Dave Guest,dave@example.com,pass123,GUEST,CERT-999,ACTIVE,External
M001,Manager Mike,manager@yorku.ca,manager123,MANAGER,MGR-001,ACTIVE,Engineering
C001,Coordinator Carol,coord@yorku.ca,coord123,COORDINATOR,COORD-001,ACTIVE,Administration
U1773892952781,shiah,shiah@gmail.com,Ilham123,STUDENT,123,PENDING,eecs
U1773922455105,Jane Doe,jane@yorku.ca,pass123,FACULTY,S12345,PENDING,Engineering
================================================
FILE: D2/src/Main.java
================================================
import javax.swing.SwingUtilities;
import gui.MainFrame;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
MainFrame.getInstance().setVisible(true);
});
}
}
================================================
FILE: D2/src/data/.gitkeep
================================================
================================================
FILE: D2/src/data/BookingDAO.java
================================================
package data;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import model.Reservation;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
/**
* BookingDAO - loads and saves reservations from/to bookings.csv
* CSV columns: bookingID,userID,equipmentID,startTime,endTime,status,depositAmount,depositForfeited,paymentMethod,isExtended
*/
public class BookingDAO {
private String path;
private List bookings = new ArrayList<>();
public BookingDAO(String path) {
this.path = path;
}
public void load() throws Exception {
bookings.clear();
CsvReader reader = new CsvReader(path);
reader.readHeaders();
while (reader.readRecord()) {
Reservation r = new Reservation();
r.setBookingID(reader.get("bookingID"));
r.setUserID(reader.get("userID"));
r.setEquipmentID(reader.get("equipmentID"));
r.setStartTime(reader.get("startTime"));
r.setEndTime(reader.get("endTime"));
r.setStatus(reader.get("status"));
r.setDepositAmount(Double.parseDouble(reader.get("depositAmount")));
r.setDepositForfeited(Boolean.parseBoolean(reader.get("depositForfeited")));
r.setPaymentMethod(reader.get("paymentMethod"));
r.setExtended(Boolean.parseBoolean(reader.get("isExtended")));
bookings.add(r);
}
reader.close();
}
public void save() throws Exception {
CsvWriter writer = new CsvWriter(new FileWriter(path, false), ',');
writer.write("bookingID");
writer.write("userID");
writer.write("equipmentID");
writer.write("startTime");
writer.write("endTime");
writer.write("status");
writer.write("depositAmount");
writer.write("depositForfeited");
writer.write("paymentMethod");
writer.write("isExtended");
writer.endRecord();
for (Reservation r : bookings) {
writer.write(r.getBookingID());
writer.write(r.getUserID());
writer.write(r.getEquipmentID());
writer.write(r.getStartTime());
writer.write(r.getEndTime());
writer.write(r.getStatus());
writer.write(String.valueOf(r.getDepositAmount()));
writer.write(String.valueOf(r.isDepositForfeited()));
writer.write(r.getPaymentMethod());
writer.write(String.valueOf(r.isExtended()));
writer.endRecord();
}
writer.close();
}
public void addBooking(Reservation r) {
bookings.add(r);
}
public Reservation findByID(String bookingID) {
for (Reservation r : bookings) {
if (r.getBookingID().equals(bookingID)) return r;
}
return null;
}
public List getBookingsByUser(String userID) {
List result = new ArrayList<>();
for (Reservation r : bookings) {
if (r.getUserID().equals(userID)) result.add(r);
}
return result;
}
public List getBookingsByEquipment(String equipmentID) {
List result = new ArrayList<>();
for (Reservation r : bookings) {
if (r.getEquipmentID().equals(equipmentID)) result.add(r);
}
return result;
}
public List getAllBookings() {
return bookings;
}
public void updateBooking(Reservation updated) {
for (int i = 0; i < bookings.size(); i++) {
if (bookings.get(i).getBookingID().equals(updated.getBookingID())) {
bookings.set(i, updated);
return;
}
}
}
}
================================================
FILE: D2/src/data/EquipmentDAO.java
================================================
package data;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import model.Equipment;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
/**
* EquipmentDAO - loads and saves equipment from/to equipment.csv
* CSV columns: equipmentID,name,description,type,status,buildingName,roomNumber
*/
public class EquipmentDAO {
private String path;
private List equipmentList = new ArrayList<>();
public EquipmentDAO(String path) {
this.path = path;
}
public void load() throws Exception {
equipmentList.clear();
CsvReader reader = new CsvReader(path);
reader.readHeaders();
while (reader.readRecord()) {
Equipment e = new Equipment();
e.setEquipmentID(reader.get("equipmentID"));
e.setName(reader.get("name"));
e.setDescription(reader.get("description"));
e.setType(reader.get("type"));
e.setStatus(reader.get("status"));
e.setBuildingName(reader.get("buildingName"));
e.setRoomNumber(reader.get("roomNumber"));
equipmentList.add(e);
}
reader.close();
}
public void save() throws Exception {
CsvWriter writer = new CsvWriter(new FileWriter(path, false), ',');
writer.write("equipmentID");
writer.write("name");
writer.write("description");
writer.write("type");
writer.write("status");
writer.write("buildingName");
writer.write("roomNumber");
writer.endRecord();
for (Equipment e : equipmentList) {
writer.write(e.getEquipmentID());
writer.write(e.getName());
writer.write(e.getDescription());
writer.write(e.getType());
writer.write(e.getStatus());
writer.write(e.getBuildingName());
writer.write(e.getRoomNumber());
writer.endRecord();
}
writer.close();
}
public void addEquipment(Equipment e) {
equipmentList.add(e);
}
public Equipment findByID(String equipmentID) {
for (Equipment e : equipmentList) {
if (e.getEquipmentID().equals(equipmentID)) return e;
}
return null;
}
public List getAllEquipment() {
return equipmentList;
}
public List getAvailableEquipment() {
List available = new ArrayList<>();
for (Equipment e : equipmentList) {
if (e.isAvailable()) available.add(e);
}
return available;
}
public void updateEquipment(Equipment updated) {
for (int i = 0; i < equipmentList.size(); i++) {
if (equipmentList.get(i).getEquipmentID().equals(updated.getEquipmentID())) {
equipmentList.set(i, updated);
return;
}
}
}
}
================================================
FILE: D2/src/data/UserDAO.java
================================================
package data;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import model.*;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
/**
* UserDAO - loads and saves users from/to users.csv
* CSV columns: userID,name,email,password,userType,staffID,status,department
*/
public class UserDAO {
private String path;
private List users = new ArrayList<>();
public UserDAO(String path) {
this.path = path;
}
public void load() throws Exception {
users.clear();
CsvReader reader = new CsvReader(path);
reader.readHeaders();
while (reader.readRecord()) {
String userType = reader.get("userType");
User user = createUser(userType);
user.setUserID(reader.get("userID"));
user.setName(reader.get("name"));
user.setEmail(reader.get("email"));
user.setPassword(reader.get("password"));
user.setUserType(userType);
user.setStaffID(reader.get("staffID"));
user.setStatus(reader.get("status"));
user.setDepartment(reader.get("department"));
users.add(user);
}
reader.close();
}
public void save() throws Exception {
CsvWriter writer = new CsvWriter(new FileWriter(path, false), ',');
// Write headers
writer.write("userID");
writer.write("name");
writer.write("email");
writer.write("password");
writer.write("userType");
writer.write("staffID");
writer.write("status");
writer.write("department");
writer.endRecord();
// Write each user
for (User u : users) {
writer.write(u.getUserID());
writer.write(u.getName());
writer.write(u.getEmail());
writer.write(u.getPassword());
writer.write(u.getUserType());
writer.write(u.getStaffID());
writer.write(u.getStatus());
writer.write(u.getDepartment());
writer.endRecord();
}
writer.close();
}
public void addUser(User user) {
users.add(user);
}
public User findByEmail(String email) {
for (User u : users) {
if (u.getEmail().equalsIgnoreCase(email)) return u;
}
return null;
}
public User findByID(String userID) {
for (User u : users) {
if (u.getUserID().equals(userID)) return u;
}
return null;
}
public boolean emailExists(String email) {
return findByEmail(email) != null;
}
public List getAllUsers() {
return users;
}
public void updateUser(User updated) {
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getUserID().equals(updated.getUserID())) {
users.set(i, updated);
return;
}
}
}
// Factory helper - creates the right subclass based on userType string
private User createUser(String userType) {
switch (userType.toUpperCase()) {
case "STUDENT": return new Student();
case "FACULTY": return new Faculty();
case "RESEARCHER": return new Researcher();
case "GUEST": return new Guest();
case "MANAGER": return new LabManager();
case "COORDINATOR": return new HeadLabCoordinator();
default: return new Student();
}
}
}
================================================
FILE: D2/src/gui/.gitkeep
================================================
================================================
FILE: D2/src/gui/CoordinatorDashboardPanel.java
================================================
package gui;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import data.UserDAO;
import model.*;
import pattern.singleton.HeadLabCoordinatorSingleton;
public class CoordinatorDashboardPanel extends ManagerDashboardPanel {
private static final long serialVersionUID = 1L;
private DefaultTableModel allUsersModel;
private JTable allUsersTable;
private JTextField genID, genName, genEmail, genDept;
private JLabel coordInfo;
public CoordinatorDashboardPanel(MainFrame frame) {
super(frame, false); // don't call build() yet
build();
}
@Override
public void refresh() {
try { equipDAO.load(); userDAO.load(); bookDAO.load(); } catch (Exception e) { e.printStackTrace(); }
if (currentUser != null && coordInfo != null)
coordInfo.setText(" Head Lab Coordinator | " + currentUser.getName());
refreshEquip();
refreshPending();
refreshAllBookings();
refreshAllUsers();
}
@Override
protected void build() {
setBackground(UI.BG);
setLayout(new BorderLayout());
JButton logout = UI.button("Logout", UI.DANGER);
logout.addActionListener(e -> getFrame().showPanel(MainFrame.LOGIN));
coordInfo = new JLabel(" ");
coordInfo.setForeground(new Color(148,163,184));
coordInfo.setFont(new Font("SansSerif", Font.PLAIN, 12));
JPanel nav = UI.navBar("👑 Coordinator Console", "", logout);
nav.add(coordInfo, BorderLayout.CENTER);
add(nav, BorderLayout.NORTH);
JTabbedPane tabs = new JTabbedPane();
tabs.setFont(new Font("SansSerif", Font.BOLD, 12));
tabs.addTab("⚠ Pending Approvals", buildPendingTab());
tabs.addTab("🔬 Equipment", buildEquipTab());
tabs.addTab("➕ Add Equipment", buildAddTab());
tabs.addTab("📋 All Bookings", buildBookingsTab());
tabs.addTab("👥 All Users", buildAllUsersTab());
tabs.addTab("🔑 Generate Manager", buildGenerateTab());
add(tabs, BorderLayout.CENTER);
status = UI.statusBar();
add(status, BorderLayout.SOUTH);
}
private JPanel buildAllUsersTab() {
JPanel p = new JPanel(new BorderLayout(10, 10));
p.setBackground(UI.BG);
p.setBorder(BorderFactory.createEmptyBorder(14, 14, 14, 14));
p.add(UI.heading("All Registered Users"), BorderLayout.NORTH);
allUsersModel = UI.tableModel("User ID","Name","Email","Type","Staff ID","Department","Status");
allUsersTable = UI.styledTable(allUsersModel);
p.add(new JScrollPane(allUsersTable), BorderLayout.CENTER);
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 8));
btns.setBackground(UI.BG);
JButton refresh = UI.button("↻ Refresh", UI.YORKU_RED);
refresh.addActionListener(e -> refreshAllUsers());
JButton approve = UI.button("✔ Approve Selected", UI.SUCCESS);
approve.addActionListener(e -> approveFromAllUsers());
JButton reject = UI.button("✘ Reject Selected", UI.DANGER);
reject.addActionListener(e -> rejectFromAllUsers());
btns.add(refresh); btns.add(approve); btns.add(reject);
p.add(btns, BorderLayout.SOUTH);
return p;
}
private JPanel buildGenerateTab() {
JPanel outer = new JPanel(new GridBagLayout());
outer.setBackground(UI.BG);
JPanel form = UI.card("Generate Lab Manager Account (Req2)");
JLabel note = new JLabel("Only the Head Lab Coordinator can perform this action (Req2).");
note.setFont(new Font("SansSerif", Font.ITALIC, 12));
note.setForeground(UI.TEXT_MUTED);
note.setAlignmentX(Component.LEFT_ALIGNMENT);
form.add(note);
form.add(Box.createVerticalStrut(12));
genID = addField(form, "Manager ID (e.g. M002)");
genName = addField(form, "Full Name");
genEmail = addField(form, "Email Address");
genDept = addField(form, "Department");
form.add(Box.createVerticalStrut(8));
JButton gen = UI.button("Generate Manager Account", UI.YORKU_RED);
gen.setAlignmentX(Component.LEFT_ALIGNMENT);
gen.addActionListener(e -> generateManager());
form.add(gen);
JPanel wrap = new JPanel(new BorderLayout());
wrap.setBackground(UI.CARD);
wrap.setBorder(BorderFactory.createLineBorder(UI.BORDER));
wrap.add(form, BorderLayout.CENTER);
outer.add(wrap);
return outer;
}
private void refreshAllUsers() {
if (allUsersModel == null) return;
try { userDAO.load(); } catch (Exception e) { e.printStackTrace(); }
allUsersModel.setRowCount(0);
for (User u : userDAO.getAllUsers())
allUsersModel.addRow(new Object[]{
u.getUserID(), u.getName(), u.getEmail(),
u.getUserType(), u.getStaffID(), u.getDepartment(), u.getStatus()
});
}
private void approveFromAllUsers() {
int row = allUsersTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select a user.", true); return; }
String uid = (String) allUsersModel.getValueAt(row, 0);
String name = (String) allUsersModel.getValueAt(row, 1);
User u = userDAO.findByID(uid);
if (u == null) return;
u.setStatus("ACTIVE");
userDAO.updateUser(u);
try { userDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "✔ Approved: " + name, false);
refreshAllUsers(); refreshPending();
}
private void rejectFromAllUsers() {
int row = allUsersTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select a user.", true); return; }
String uid = (String) allUsersModel.getValueAt(row, 0);
String name = (String) allUsersModel.getValueAt(row, 1);
User u = userDAO.findByID(uid);
if (u == null) return;
u.setStatus("REJECTED");
userDAO.updateUser(u);
try { userDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "✘ Rejected: " + name, false);
refreshAllUsers(); refreshPending();
}
private void generateManager() {
if (currentUser == null || !"COORDINATOR".equals(currentUser.getUserType())) {
UI.setStatus(status, "Only the Head Lab Coordinator can do this (Req2).", true); return;
}
String id = genID.getText().trim();
String name = genName.getText().trim();
String email = genEmail.getText().trim();
String dept = genDept.getText().trim();
if (id.isEmpty() || name.isEmpty() || email.isEmpty()) {
UI.setStatus(status, "All fields required.", true); return;
}
try { userDAO.load(); } catch (Exception e) { e.printStackTrace(); }
if (userDAO.emailExists(email)) {
UI.setStatus(status, "Email already exists.", true); return;
}
String tempPass = "temp" + id;
LabManager mgr = HeadLabCoordinatorSingleton.getInstance()
.generateManagerAccount(id, name, email, tempPass, dept);
userDAO.addUser(mgr);
try { userDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status,
"✔ Manager account created! Email: " + email + " | Temp Password: " + tempPass, false);
genID.setText(""); genName.setText(""); genEmail.setText(""); genDept.setText("");
refreshAllUsers();
}
private MainFrame getFrame() {
Container c = getParent();
while (c != null && !(c instanceof MainFrame)) c = c.getParent();
return (MainFrame) c;
}
}
================================================
FILE: D2/src/gui/LoginPanel.java
================================================
package gui;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import data.UserDAO;
import model.User;
import pattern.factory.UserFactory;
public class LoginPanel extends JPanel {
private static final long serialVersionUID = 1L;
private MainFrame frame;
private UserDAO userDAO = new UserDAO("data/users.csv");
private JTextField emailField;
private JPasswordField passField;
private JTextField regName, regEmail, regStaffID, regDept;
private JPasswordField regPass;
private JComboBox regType;
private JLabel status;
public LoginPanel(MainFrame frame) {
this.frame = frame;
try { userDAO.load(); } catch (Exception e) { e.printStackTrace(); }
build();
}
private void build() {
setBackground(UI.BG);
setLayout(new BorderLayout());
// Nav bar
JPanel nav = new JPanel(new BorderLayout());
nav.setBackground(UI.NAV_BG);
nav.setBorder(BorderFactory.createMatteBorder(0, 0, 4, 0, UI.YORKU_RED));
JPanel navLeft = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
navLeft.setBackground(UI.NAV_BG);
JLabel badge = new JLabel(" Y ");
badge.setFont(new Font("Segoe UI", Font.BOLD, 15));
badge.setForeground(Color.WHITE); badge.setBackground(UI.YORKU_RED); badge.setOpaque(true);
badge.setBorder(BorderFactory.createEmptyBorder(13,14,13,14));
JLabel title = new JLabel(" YorkU Lab Equipment Reservation System");
title.setFont(UI.F_HEAD); title.setForeground(UI.NAV_FG);
navLeft.add(badge); navLeft.add(title);
nav.add(navLeft, BorderLayout.WEST);
add(nav, BorderLayout.NORTH);
// Cards
JPanel centre = new JPanel(new GridBagLayout());
centre.setBackground(UI.BG);
JPanel cards = new JPanel(new GridLayout(1, 2, 24, 0));
cards.setBackground(UI.BG);
cards.setPreferredSize(new Dimension(940, 560));
cards.add(buildLoginCard());
cards.add(buildRegCard());
centre.add(cards);
add(centre, BorderLayout.CENTER);
// Status
status = UI.statusBar();
status.setText(" Welcome — please sign in or create an account.");
add(status, BorderLayout.SOUTH);
}
private JPanel buildLoginCard() {
JPanel inner = UI.card("Sign In");
inner.add(UI.label("Email address")); inner.add(Box.createVerticalStrut(4));
emailField = UI.field("alice@yorku.ca"); inner.add(emailField);
inner.add(Box.createVerticalStrut(14));
inner.add(UI.label("Password")); inner.add(Box.createVerticalStrut(4));
passField = UI.password();
passField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
passField.setAlignmentX(Component.LEFT_ALIGNMENT);
inner.add(passField); inner.add(Box.createVerticalStrut(22));
JButton btn = UI.primary("Login");
btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 42));
btn.setAlignmentX(Component.LEFT_ALIGNMENT);
btn.addActionListener(e -> handleLogin());
inner.add(btn); inner.add(Box.createVerticalStrut(20));
// Red hint box
JPanel hint = new JPanel();
hint.setLayout(new BoxLayout(hint, BoxLayout.Y_AXIS));
hint.setBackground(new Color(0xfb, 0xe9, 0xec));
hint.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(new Color(0xe3,0x18,0x37,80), 1, true),
BorderFactory.createEmptyBorder(10,12,10,12)));
hint.setAlignmentX(Component.LEFT_ALIGNMENT);
hint.setMaximumSize(new Dimension(Integer.MAX_VALUE, 130));
JLabel ht = new JLabel("Test Accounts"); ht.setFont(UI.F_SUB);
ht.setForeground(UI.YORKU_DARK); ht.setAlignmentX(Component.LEFT_ALIGNMENT);
hint.add(ht); hint.add(Box.createVerticalStrut(6));
for (String line : new String[]{
"alice@yorku.ca / pass123 → Student",
"bob@yorku.ca / pass123 → Faculty",
"manager@yorku.ca / manager123 → Manager",
"coord@yorku.ca / coord123 → Coordinator"}) {
JLabel l = new JLabel(line); l.setFont(UI.F_SMALL); l.setForeground(UI.TEXT_MUTED);
l.setAlignmentX(Component.LEFT_ALIGNMENT); hint.add(l);
}
inner.add(hint);
return wrapCard(inner);
}
private JPanel buildRegCard() {
JPanel inner = UI.card("Create Account");
regName = UI.formField(inner, "Full Name");
regEmail = UI.formField(inner, "Email Address");
regPass = UI.formPassword(inner, "Password (min 6 characters)");
regType = UI.formCombo(inner, "User Type", "STUDENT","FACULTY","RESEARCHER","GUEST");
regStaffID = UI.formField(inner, "Staff / Student ID or Certification Number (Req8)");
regDept = UI.formField(inner, "Department");
JButton btn = UI.primary("Create Account");
btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 42));
btn.setAlignmentX(Component.LEFT_ALIGNMENT);
btn.addActionListener(e -> handleRegister());
inner.add(btn);
return wrapCard(inner);
}
private JPanel wrapCard(JPanel inner) {
JPanel w = new JPanel(new BorderLayout());
w.setBackground(UI.CARD);
w.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(UI.BORDER, 1, true),
BorderFactory.createEmptyBorder(26, 30, 26, 30)));
w.add(inner); return w;
}
private void handleLogin() {
String email = emailField.getText().trim();
String pass = new String(passField.getPassword()).trim();
if (email.isEmpty() || pass.isEmpty()) { setStatus("Please enter your email and password.", true); return; }
try { userDAO.load(); } catch (Exception e) { e.printStackTrace(); }
User user = userDAO.findByEmail(email);
if (user == null || !user.getPassword().equals(pass)) { setStatus("Invalid email or password.", true); return; }
if ("PENDING".equals(user.getStatus())) { setStatus("Account is pending departmental approval.", true); return; }
if ("REJECTED".equals(user.getStatus())) { setStatus("Account was rejected. Contact the coordinator.", true); return; }
setStatus("Welcome, " + user.getName() + "!", false);
frame.onLoginSuccess(user);
}
private void handleRegister() {
String name=regName.getText().trim(), email=regEmail.getText().trim();
String pass=new String(regPass.getPassword()).trim(), type=(String)regType.getSelectedItem();
String staffID=regStaffID.getText().trim(), dept=regDept.getText().trim();
if (name.isEmpty()||email.isEmpty()||pass.isEmpty()||staffID.isEmpty()) { setStatus("All fields are required.", true); return; }
if (pass.length()<6) { setStatus("Password must be at least 6 characters.", true); return; }
try { userDAO.load(); } catch (Exception e) { e.printStackTrace(); }
if (userDAO.emailExists(email)) { setStatus("Email is already registered.", true); return; }
UserFactory factory = new UserFactory();
User u = factory.getUserType(type);
u.setUserID("U"+System.currentTimeMillis()); u.setName(name); u.setEmail(email);
u.setPassword(pass); u.setUserType(type); u.setStaffID(staffID); u.setDepartment(dept);
u.setStatus("GUEST".equals(type)?"ACTIVE":"PENDING");
userDAO.addUser(u);
try { userDAO.save(); } catch (Exception e) { setStatus("Error saving account.",true); return; }
setStatus("GUEST".equals(type)?"Account created! You can now sign in.":"Registration submitted — awaiting departmental approval.", false);
regName.setText(""); regEmail.setText(""); regPass.setText(""); regStaffID.setText(""); regDept.setText("");
}
private void setStatus(String msg, boolean error) {
status.setText(" "+msg);
status.setForeground(error ? UI.DANGER : UI.SUCCESS);
}
}
================================================
FILE: D2/src/gui/MainFrame.java
================================================
package gui;
import javax.swing.*;
import java.awt.*;
import model.User;
public class MainFrame extends JFrame {
private static final long serialVersionUID = 1L;
private static MainFrame instance;
private CardLayout cardLayout;
private JPanel mainPanel;
private LoginPanel loginPanel;
private UserDashboardPanel userDashboard;
private ManagerDashboardPanel managerDashboard;
private CoordinatorDashboardPanel coordinatorDashboard;
public static final String LOGIN = "LOGIN";
public static final String USER = "USER";
public static final String MANAGER = "MANAGER";
public static final String COORDINATOR = "COORDINATOR";
public static MainFrame getInstance() {
if (instance == null) instance = new MainFrame();
return instance;
}
private MainFrame() {
super("Lab Equipment Reservation System — York University");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1100, 700);
setLocationRelativeTo(null);
cardLayout = new CardLayout();
mainPanel = new JPanel(cardLayout);
loginPanel = new LoginPanel(this);
userDashboard = new UserDashboardPanel(this);
managerDashboard = new ManagerDashboardPanel(this);
coordinatorDashboard = new CoordinatorDashboardPanel(this);
mainPanel.add(loginPanel, LOGIN);
mainPanel.add(userDashboard, USER);
mainPanel.add(managerDashboard, MANAGER);
mainPanel.add(coordinatorDashboard, COORDINATOR);
add(mainPanel);
showPanel(LOGIN);
}
public void showPanel(String name) {
cardLayout.show(mainPanel, name);
}
public void onLoginSuccess(User user) {
String type = user.getUserType().toUpperCase();
switch (type) {
case "COORDINATOR":
coordinatorDashboard.setCurrentUser(user);
coordinatorDashboard.refresh();
showPanel(COORDINATOR);
break;
case "MANAGER":
managerDashboard.setCurrentUser(user);
managerDashboard.refresh();
showPanel(MANAGER);
break;
default: // STUDENT, FACULTY, RESEARCHER, GUEST
userDashboard.setCurrentUser(user);
userDashboard.refresh();
showPanel(USER);
break;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> MainFrame.getInstance().setVisible(true));
}
}
================================================
FILE: D2/src/gui/ManagerDashboardPanel.java
================================================
package gui;
// pre determined login
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.util.List;
import data.*;
import model.*;
import pattern.observer.*;
import pattern.state.*;
public class ManagerDashboardPanel extends JPanel {
private static final long serialVersionUID = 1L;
private MainFrame frame;
protected User currentUser;
protected EquipmentDAO equipDAO = new EquipmentDAO("data/equipment.csv");
protected UserDAO userDAO = new UserDAO("data/users.csv");
protected BookingDAO bookDAO = new BookingDAO("data/bookings.csv");
protected DefaultTableModel equipModel, pendModel, bookModel;
protected JTable equipTable, pendTable, bookTable;
private JTextField eqID, eqName, eqDesc, eqType, eqBuilding, eqRoom;
protected JLabel status;
private JLabel navInfo;
public ManagerDashboardPanel(MainFrame frame) { this(frame, true); }
protected ManagerDashboardPanel(MainFrame frame, boolean standalone) {
this.frame = frame;
if (standalone) build();
}
public void setCurrentUser(User u) { this.currentUser = u; }
public void refresh() {
try { equipDAO.load(); userDAO.load(); bookDAO.load(); } catch (Exception e) { e.printStackTrace(); }
if (currentUser != null && navInfo != null)
navInfo.setText(" Lab Manager | " + currentUser.getName());
refreshEquip();
refreshPending();
refreshAllBookings();
}
protected void build() {
setBackground(UI.BG);
setLayout(new BorderLayout());
JButton logout = UI.button("Logout", UI.DANGER);
logout.addActionListener(e -> frame.showPanel(MainFrame.LOGIN));
navInfo = new JLabel(" ");
navInfo.setForeground(new Color(148,163,184));
navInfo.setFont(new Font("SansSerif", Font.PLAIN, 12));
JPanel nav = UI.navBar("🔧 Manager Console", "", logout);
nav.add(navInfo, BorderLayout.CENTER);
add(nav, BorderLayout.NORTH);
JTabbedPane tabs = buildTabs();
add(tabs, BorderLayout.CENTER);
status = UI.statusBar();
add(status, BorderLayout.SOUTH);
}
protected JTabbedPane buildTabs() {
JTabbedPane tabs = new JTabbedPane();
tabs.setFont(new Font("SansSerif", Font.BOLD, 12));
tabs.addTab("⚠ Pending Approvals", buildPendingTab());
tabs.addTab("🔬 Equipment", buildEquipTab());
tabs.addTab("➕ Add Equipment", buildAddTab());
tabs.addTab("📋 All Bookings", buildBookingsTab());
return tabs;
}
protected JPanel buildPendingTab() {
JPanel p = new JPanel(new BorderLayout(10, 10));
p.setBackground(UI.BG);
p.setBorder(BorderFactory.createEmptyBorder(14,14,14,14));
p.add(UI.heading("Pending Account Approvals (Req1)"), BorderLayout.NORTH);
pendModel = UI.tableModel("User ID","Name","Email","Type","Staff/Cert ID","Department","Status");
pendTable = UI.styledTable(pendModel);
pendTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
p.add(new JScrollPane(pendTable), BorderLayout.CENTER);
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 8));
btns.setBackground(UI.BG);
JButton approve = UI.button("✔ Approve", UI.SUCCESS);
JButton reject = UI.button("✘ Reject", UI.DANGER);
JButton refresh = UI.button("↻ Refresh", UI.YORKU_RED);
approve.addActionListener(e -> approve());
reject.addActionListener(e -> reject());
refresh.addActionListener(e -> refreshPending());
btns.add(approve); btns.add(reject); btns.add(refresh);
p.add(btns, BorderLayout.SOUTH);
return p;
}
protected JPanel buildEquipTab() {
JPanel p = new JPanel(new BorderLayout(10, 10));
p.setBackground(UI.BG);
p.setBorder(BorderFactory.createEmptyBorder(14,14,14,14));
p.add(UI.heading("Equipment Management (Req6)"), BorderLayout.NORTH);
equipModel = UI.tableModel("ID","Name","Description","Type","Status","Building","Room");
equipTable = UI.styledTable(equipModel);
equipTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
p.add(new JScrollPane(equipTable), BorderLayout.CENTER);
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 8));
btns.setBackground(UI.BG);
JButton enable = UI.button("✔ Enable", UI.SUCCESS);
JButton disable = UI.button("✘ Disable", UI.WARNING);
JButton maint = UI.button("🔧 Maintenance", UI.DANGER);
enable.addActionListener(e -> changeStatus("AVAILABLE"));
disable.addActionListener(e -> changeStatus("DISABLED"));
maint.addActionListener(e -> changeStatus("MAINTENANCE"));
btns.add(enable); btns.add(disable); btns.add(maint);
p.add(btns, BorderLayout.SOUTH);
return p;
}
protected JPanel buildAddTab() {
JPanel outer = new JPanel(new GridBagLayout());
outer.setBackground(UI.BG);
JPanel form = UI.card("Add New Equipment (Req6, Req7)");
eqID = addField(form, "Equipment ID (required — Req7)");
eqName = addField(form, "Name");
eqDesc = addField(form, "Description (required — Req7)");
eqType = addField(form, "Type");
eqBuilding = addField(form, "Building Name (required — Req7)");
eqRoom = addField(form, "Room Number (required — Req7)");
form.add(Box.createVerticalStrut(12));
JButton add = UI.button("Add Equipment", UI.YORKU_RED);
add.setAlignmentX(Component.LEFT_ALIGNMENT);
add.addActionListener(e -> addEquipment());
form.add(add);
JPanel wrap = new JPanel(new BorderLayout());
wrap.setBackground(UI.CARD);
wrap.setBorder(BorderFactory.createLineBorder(UI.BORDER));
wrap.add(form, BorderLayout.CENTER);
outer.add(wrap);
return outer;
}
protected JPanel buildBookingsTab() {
JPanel p = new JPanel(new BorderLayout(10, 10));
p.setBackground(UI.BG);
p.setBorder(BorderFactory.createEmptyBorder(14,14,14,14));
p.add(UI.heading("All Bookings / Usage Report (Req5, Req8)"), BorderLayout.NORTH);
bookModel = UI.tableModel("Booking ID","User","Equipment","Start","End","Status","Deposit","Payment");
bookTable = UI.styledTable(bookModel);
p.add(new JScrollPane(bookTable), BorderLayout.CENTER);
JButton refresh = UI.button("↻ Refresh", UI.YORKU_RED);
refresh.addActionListener(e -> refreshAllBookings());
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 8));
btns.setBackground(UI.BG);
btns.add(refresh);
p.add(btns, BorderLayout.SOUTH);
return p;
}
protected void refreshEquip() {
if (equipModel == null) return;
equipModel.setRowCount(0);
for (Equipment eq : equipDAO.getAllEquipment())
equipModel.addRow(new Object[]{
eq.getEquipmentID(), eq.getName(), eq.getDescription(),
eq.getType(), eq.getStatus(), eq.getBuildingName(), eq.getRoomNumber()
});
}
protected void refreshPending() {
if (pendModel == null) return;
try { userDAO.load(); } catch (Exception e) { e.printStackTrace(); }
pendModel.setRowCount(0);
int count = 0;
for (User u : userDAO.getAllUsers()) {
if ("PENDING".equals(u.getStatus())) {
pendModel.addRow(new Object[]{
u.getUserID(), u.getName(), u.getEmail(),
u.getUserType(), u.getStaffID(), u.getDepartment(), u.getStatus()
});
count++;
}
}
if (status != null)
UI.setStatus(status, count > 0 ? count + " pending approval(s)" : "No pending approvals", count > 0);
}
protected void refreshAllBookings() {
if (bookModel == null) return;
try { bookDAO.load(); } catch (Exception e) { e.printStackTrace(); }
bookModel.setRowCount(0);
for (Reservation r : bookDAO.getAllBookings())
bookModel.addRow(new Object[]{
r.getBookingID(), r.getUserID(), r.getEquipmentID(),
r.getStartTime(), r.getEndTime(), r.getStatus(),
"$"+r.getDepositAmount(), r.getPaymentMethod()
});
}
private void approve() {
int row = pendTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select a user to approve.", true); return; }
String uid = (String) pendModel.getValueAt(row, 0);
String name = (String) pendModel.getValueAt(row, 1);
User u = userDAO.findByID(uid);
if (u == null) return;
u.setStatus("ACTIVE");
userDAO.updateUser(u);
try { userDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "✔ Approved: " + name + ". They can now log in.", false);
refreshPending();
}
private void reject() {
int row = pendTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select a user to reject.", true); return; }
String uid = (String) pendModel.getValueAt(row, 0);
String name = (String) pendModel.getValueAt(row, 1);
User u = userDAO.findByID(uid);
if (u == null) return;
u.setStatus("REJECTED");
userDAO.updateUser(u);
try { userDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "✘ Rejected: " + name, false);
refreshPending();
}
protected void changeStatus(String newStatus) {
int row = equipTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select equipment.", true); return; }
String eqid = (String) equipModel.getValueAt(row, 0);
Equipment eq = equipDAO.findByID(eqid);
if (eq == null) return;
EquipmentContext ctx = new EquipmentContext(eqid, eq.getStatus());
List affected = new java.util.ArrayList<>();
for (Reservation r : bookDAO.getBookingsByEquipment(eqid))
if ("CONFIRMED".equals(r.getStatus())) {
User u = userDAO.findByID(r.getUserID());
if (u != null) affected.add(u);
}
UserNotificationObserver notifier = new UserNotificationObserver(affected);
ctx.registerObserver(notifier);
switch (newStatus) {
case "AVAILABLE": ctx.enable(); break;
case "DISABLED": ctx.disable(); break;
case "MAINTENANCE": ctx.markUnderMaintenance(); break;
}
eq.setStatus(ctx.getStatusString());
equipDAO.updateEquipment(eq);
try { equipDAO.save(); } catch (Exception e) { e.printStackTrace(); }
String log = notifier.getNotificationLog();
UI.setStatus(status, eqid + " → " + newStatus
+ (log.isEmpty() ? "" : " | Notified " + affected.size() + " user(s)"), false);
refreshEquip();
}
private void addEquipment() {
String id = eqID.getText().trim(), name = eqName.getText().trim();
String desc = eqDesc.getText().trim(), type = eqType.getText().trim();
String bld = eqBuilding.getText().trim(), room = eqRoom.getText().trim();
if (id.isEmpty() || name.isEmpty() || desc.isEmpty() || bld.isEmpty() || room.isEmpty()) {
UI.setStatus(status, "ID, Name, Description, Building and Room are required (Req7).", true); return;
}
if (equipDAO.findByID(id) != null) { UI.setStatus(status, "Equipment ID already exists.", true); return; }
equipDAO.addEquipment(new Equipment(id, name, desc, type, "AVAILABLE", bld, room));
try { equipDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "Equipment " + id + " added.", false);
refreshEquip();
eqID.setText(""); eqName.setText(""); eqDesc.setText(""); eqType.setText(""); eqBuilding.setText(""); eqRoom.setText("");
}
protected JTextField addField(JPanel form, String labelText) {
form.add(UI.label(labelText));
form.add(Box.createVerticalStrut(2));
JTextField f = UI.field("");
f.setMaximumSize(new Dimension(380, 34));
form.add(f);
form.add(Box.createVerticalStrut(10));
return f;
}
}
================================================
FILE: D2/src/gui/UI.java
================================================
package gui;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
public class UI {
public static final Color YORKU_RED = new Color(0xe3, 0x18, 0x37);
public static final Color YORKU_DARK = new Color(0xb8, 0x10, 0x2a);
public static final Color BG = new Color(0xf5, 0xf6, 0xf8);
public static final Color CARD = Color.WHITE;
public static final Color NAV_BG = new Color(0x1a, 0x1a, 0x2e);
public static final Color NAV_FG = new Color(0xe8, 0xe8, 0xf0);
public static final Color HEADER_BG = new Color(0xf8, 0xf9, 0xfa);
public static final Color BORDER = new Color(0xd8, 0xda, 0xde);
public static final Color ROW_ALT = new Color(0xfa, 0xfb, 0xfc);
public static final Color ROW_SEL_BG = new Color(0xfb, 0xe9, 0xec);
public static final Color ROW_SEL_FG = new Color(0x8c, 0x0b, 0x1e);
public static final Color TEXT = new Color(0x1a, 0x1a, 0x2e);
public static final Color TEXT_MUTED = new Color(0x6c, 0x75, 0x7d);
public static final Color SUCCESS = new Color(0x19, 0x87, 0x54);
public static final Color DANGER = new Color(0xdc, 0x35, 0x45);
public static final Color WARNING = new Color(0xd9, 0x73, 0x06);
public static final Color INFO = new Color(0x0d, 0x6e, 0xfd);
public static final Color SECONDARY = new Color(0x6c, 0x75, 0x7d);
public static final Font F_TITLE = new Font("Segoe UI", Font.BOLD, 22);
public static final Font F_HEAD = new Font("Segoe UI", Font.BOLD, 16);
public static final Font F_SUB = new Font("Segoe UI", Font.BOLD, 13);
public static final Font F_BODY = new Font("Segoe UI", Font.PLAIN, 13);
public static final Font F_SMALL = new Font("Segoe UI", Font.PLAIN, 11);
public static final Font F_LABEL = new Font("Segoe UI", Font.BOLD, 11);
public static JButton button(String text, Color bg) {
Color hover = bg.darker();
JButton b = new JButton(text);
b.setFont(F_SUB); b.setForeground(Color.WHITE); b.setBackground(bg);
b.setOpaque(true); b.setBorderPainted(false); b.setFocusPainted(false);
b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
b.setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16));
b.addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e) { b.setBackground(hover); }
public void mouseExited(MouseEvent e) { b.setBackground(bg); }
});
return b;
}
public static JButton primary(String t) { return button(t, YORKU_RED); }
public static JButton secondary(String t) { return button(t, SECONDARY); }
public static JButton success(String t) { return button(t, SUCCESS); }
public static JButton danger(String t) { return button(t, DANGER); }
public static JButton warning(String t) { return button(t, WARNING); }
public static JButton info(String t) { return button(t, INFO); }
public static JTextField field(String placeholder) {
JTextField f = new JTextField(placeholder);
styleInput(f); return f;
}
public static JPasswordField password() {
JPasswordField f = new JPasswordField();
styleInput(f); return f;
}
public static JComboBox combo(String... items) {
JComboBox c = new JComboBox<>(items);
c.setFont(F_BODY); c.setBackground(CARD);
c.setBorder(inputBorder());
c.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
c.setAlignmentX(Component.LEFT_ALIGNMENT);
return c;
}
private static void styleInput(JTextField f) {
f.setFont(F_BODY); f.setBackground(CARD);
f.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
f.setAlignmentX(Component.LEFT_ALIGNMENT);
f.setBorder(inputBorder());
f.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
f.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(YORKU_RED, 2, true),
BorderFactory.createEmptyBorder(5, 9, 5, 9)));
}
public void focusLost(FocusEvent e) { f.setBorder(inputBorder()); }
});
}
private static Border inputBorder() {
return BorderFactory.createCompoundBorder(
new LineBorder(BORDER, 1, true),
BorderFactory.createEmptyBorder(6, 10, 6, 10));
}
public static JLabel heading(String t) {
JLabel l = new JLabel(t); l.setFont(F_HEAD); l.setForeground(TEXT); return l;
}
public static JLabel label(String t) {
JLabel l = new JLabel(t); l.setFont(F_LABEL); l.setForeground(TEXT_MUTED);
l.setAlignmentX(Component.LEFT_ALIGNMENT);
l.setBorder(BorderFactory.createEmptyBorder(0,0,2,0)); return l;
}
public static JPanel navBar(String appTitle, String userInfo, JButton... actions) {
JPanel bar = new JPanel(new BorderLayout());
bar.setBackground(NAV_BG);
bar.setBorder(BorderFactory.createMatteBorder(0, 0, 3, 0, YORKU_RED));
JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
left.setBackground(NAV_BG);
JLabel badge = new JLabel(" Y ");
badge.setFont(new Font("Segoe UI", Font.BOLD, 14));
badge.setForeground(Color.WHITE); badge.setBackground(YORKU_RED); badge.setOpaque(true);
badge.setBorder(BorderFactory.createEmptyBorder(10,12,10,12));
left.add(badge);
JLabel title = new JLabel(" " + appTitle);
title.setFont(F_HEAD); title.setForeground(NAV_FG);
left.add(title);
JLabel info = new JLabel(userInfo);
info.setFont(F_SMALL); info.setForeground(new Color(0xa0,0xa8,0xb8));
info.setHorizontalAlignment(SwingConstants.CENTER);
JPanel right = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 8));
right.setBackground(NAV_BG);
for (JButton a : actions) right.add(a);
bar.add(left, BorderLayout.WEST);
bar.add(info, BorderLayout.CENTER);
bar.add(right, BorderLayout.EAST);
return bar;
}
public static JPanel card(String title) {
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
p.setBackground(CARD);
if (title != null) {
JLabel h = new JLabel(title); h.setFont(F_HEAD); h.setForeground(TEXT);
h.setAlignmentX(Component.LEFT_ALIGNMENT);
h.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
p.add(h);
JSeparator sep = new JSeparator();
sep.setForeground(new Color(0xe3,0x18,0x37,60));
sep.setMaximumSize(new Dimension(Integer.MAX_VALUE, 2));
p.add(sep); p.add(Box.createVerticalStrut(14));
}
return p;
}
public static JLabel statusBar() {
JLabel l = new JLabel(" Ready"); l.setFont(F_SMALL); l.setForeground(TEXT_MUTED);
l.setBackground(HEADER_BG); l.setOpaque(true);
l.setBorder(BorderFactory.createMatteBorder(1,0,0,0,BORDER));
l.setPreferredSize(new Dimension(0, 28)); return l;
}
public static void setStatus(JLabel bar, String msg, boolean error) {
bar.setText(" " + msg);
bar.setForeground(error ? DANGER : SUCCESS);
}
public static DefaultTableModel tableModel(String... cols) {
return new DefaultTableModel(cols, 0) {
public boolean isCellEditable(int r, int c) { return false; }
};
}
public static JTable styledTable(DefaultTableModel model) {
JTable t = new JTable(model) {
public Component prepareRenderer(TableCellRenderer r, int row, int col) {
Component c = super.prepareRenderer(r, row, col);
if (isRowSelected(row)) { c.setBackground(ROW_SEL_BG); c.setForeground(ROW_SEL_FG); }
else { c.setBackground(row%2==0?CARD:ROW_ALT); c.setForeground(TEXT); }
return c;
}
};
t.setFont(F_BODY); t.setRowHeight(30); t.setShowVerticalLines(false);
t.setShowHorizontalLines(true); t.setGridColor(new Color(0xee,0xee,0xf2));
t.setIntercellSpacing(new Dimension(0,0));
t.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
t.setFillsViewportHeight(true);
JTableHeader hdr = t.getTableHeader();
hdr.setFont(F_SUB); hdr.setForeground(TEXT_MUTED); hdr.setBackground(HEADER_BG);
hdr.setBorder(BorderFactory.createMatteBorder(0,0,2,0,BORDER));
hdr.setReorderingAllowed(false);
hdr.setPreferredSize(new Dimension(hdr.getPreferredSize().width, 36));
return t;
}
public static JScrollPane scroll(JTable t) {
JScrollPane sp = new JScrollPane(t);
sp.setBorder(BorderFactory.createLineBorder(BORDER));
sp.getViewport().setBackground(CARD); return sp;
}
public static JTabbedPane tabs() {
JTabbedPane tp = new JTabbedPane();
tp.setFont(F_SUB); tp.setBackground(BG); tp.setBorder(null); return tp;
}
public static JTextField formField(JPanel form, String lbl) {
form.add(label(lbl)); form.add(Box.createVerticalStrut(3));
JTextField f = field(""); f.setMaximumSize(new Dimension(400,36)); form.add(f);
form.add(Box.createVerticalStrut(12)); return f;
}
public static JPasswordField formPassword(JPanel form, String lbl) {
form.add(label(lbl)); form.add(Box.createVerticalStrut(3));
JPasswordField f = password(); f.setMaximumSize(new Dimension(400,36)); form.add(f);
form.add(Box.createVerticalStrut(12)); return f;
}
public static JComboBox formCombo(JPanel form, String lbl, String... items) {
form.add(label(lbl)); form.add(Box.createVerticalStrut(3));
JComboBox c = combo(items); c.setMaximumSize(new Dimension(400,36)); form.add(c);
form.add(Box.createVerticalStrut(12)); return c;
}
public static JPanel tabContent() {
JPanel p = new JPanel(new BorderLayout(0,10));
p.setBackground(BG); p.setBorder(BorderFactory.createEmptyBorder(14,14,14,14)); return p;
}
public static JPanel buttonRow(JButton... buttons) {
JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
row.setBackground(BG); row.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
for (JButton b : buttons) row.add(b); return row;
}
}
================================================
FILE: D2/src/gui/UserDashboardPanel.java
================================================
package gui;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import data.*;
import model.*;
import pattern.command.*;
import pattern.singleton.BookingSystem;
import pattern.strategy.*;
public class UserDashboardPanel extends JPanel {
private static final long serialVersionUID = 1L;
private MainFrame frame;
private User currentUser;
private EquipmentDAO equipDAO = new EquipmentDAO("data/equipment.csv");
private BookingDAO bookDAO = new BookingDAO("data/bookings.csv");
private ReservationService svc = new ReservationService();
private DefaultTableModel equipModel, bookModel;
private JTable equipTable, bookTable;
private JTextField startField, endField, extendField;
private JComboBox payCombo;
private JLabel status, userInfo;
public UserDashboardPanel(MainFrame frame) {
this.frame = frame;
build();
}
public void setCurrentUser(User u) { this.currentUser = u; }
public void refresh() {
try { equipDAO.load(); bookDAO.load(); } catch (Exception e) { e.printStackTrace(); }
if (currentUser != null)
userInfo.setText(" " + currentUser.getUserType() + " | " + currentUser.getName()
+ " | Fee: $" + currentUser.getFeeRate() + "/hr | ID: " + currentUser.getStaffID());
refreshEquip();
refreshBookings();
}
private void build() {
setBackground(UI.BG);
setLayout(new BorderLayout());
// Nav
JButton logout = UI.button("Logout", UI.DANGER);
logout.addActionListener(e -> frame.showPanel(MainFrame.LOGIN));
userInfo = new JLabel(" ");
userInfo.setFont(new Font("SansSerif", Font.PLAIN, 12));
userInfo.setForeground(new Color(148, 163, 184));
JPanel nav = UI.navBar("🔬 Lab Equipment Reservation", "", logout);
nav.add(userInfo, BorderLayout.CENTER);
add(nav, BorderLayout.NORTH);
// Content
JTabbedPane tabs = new JTabbedPane();
tabs.setFont(new Font("SansSerif", Font.BOLD, 12));
tabs.setBackground(UI.BG);
tabs.addTab("Browse & Reserve Equipment", buildBrowseTab());
tabs.addTab("My Bookings", buildBookingsTab());
add(tabs, BorderLayout.CENTER);
// Status
status = UI.statusBar();
add(status, BorderLayout.SOUTH);
}
private JPanel buildBrowseTab() {
JPanel p = new JPanel(new BorderLayout(10, 10));
p.setBackground(UI.BG);
p.setBorder(BorderFactory.createEmptyBorder(14, 14, 14, 14));
// Equipment table
equipModel = UI.tableModel("ID", "Name", "Description", "Type", "Status", "Location");
equipTable = UI.styledTable(equipModel);
equipTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane scroll = new JScrollPane(equipTable);
scroll.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(UI.BORDER), "Available Equipment (Req3)"));
p.add(scroll, BorderLayout.CENTER);
// Booking form
JPanel form = new JPanel(new GridBagLayout());
form.setBackground(UI.CARD);
form.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder(BorderFactory.createLineBorder(UI.BORDER),
"Make a Reservation (Req3, Req4, Req10)"),
BorderFactory.createEmptyBorder(10, 12, 10, 12)
));
GridBagConstraints gc = new GridBagConstraints();
gc.insets = new Insets(4, 6, 4, 6);
gc.fill = GridBagConstraints.HORIZONTAL;
gc.gridx=0; gc.gridy=0; form.add(new JLabel("Start (yyyy-MM-dd HH:mm):"), gc);
gc.gridx=1; startField = UI.field("2026-04-10 09:00"); form.add(startField, gc);
gc.gridx=2; form.add(new JLabel("End (yyyy-MM-dd HH:mm):"), gc);
gc.gridx=3; endField = UI.field("2026-04-10 11:00"); form.add(endField, gc);
gc.gridx=4; form.add(new JLabel("Payment Method (Req10):"), gc);
gc.gridx=5; payCombo = new JComboBox<>(new String[]{"CREDIT","DEBIT","INSTITUTIONAL","GRANT"});
payCombo.setFont(new Font("SansSerif", Font.PLAIN, 12));
form.add(payCombo, gc);
gc.gridx=6;
JButton resBtn = UI.button("Reserve Selected", UI.YORKU_RED);
resBtn.addActionListener(e -> reserve());
form.add(resBtn, gc);
p.add(form, BorderLayout.SOUTH);
return p;
}
private JPanel buildBookingsTab() {
JPanel p = new JPanel(new BorderLayout(10, 10));
p.setBackground(UI.BG);
p.setBorder(BorderFactory.createEmptyBorder(14, 14, 14, 14));
bookModel = UI.tableModel("Booking ID","Equipment","Start","End","Status","Deposit","Payment","Extended");
bookTable = UI.styledTable(bookModel);
bookTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane scroll = new JScrollPane(bookTable);
scroll.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(UI.BORDER), "My Bookings"));
p.add(scroll, BorderLayout.CENTER);
// Action buttons
JPanel actions = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 8));
actions.setBackground(UI.CARD);
actions.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(UI.BORDER), "Booking Actions (Req8, Req9, Req4)"));
JButton cancelBtn = UI.button("Cancel Booking (Req8)", UI.DANGER);
cancelBtn.addActionListener(e -> cancelBooking());
actions.add(cancelBtn);
actions.add(new JLabel(" Extend to (yyyy-MM-dd HH:mm):"));
extendField = UI.field("2026-04-10 13:00");
extendField.setPreferredSize(new Dimension(160, 32));
actions.add(extendField);
JButton extBtn = UI.button("Extend Reservation (Req9)", UI.YORKU_RED);
extBtn.addActionListener(e -> extend());
actions.add(extBtn);
JButton forfeitBtn = UI.button("Simulate No-Show / Forfeit Deposit (Req4)", UI.WARNING);
forfeitBtn.addActionListener(e -> forfeit());
actions.add(forfeitBtn);
p.add(actions, BorderLayout.SOUTH);
return p;
}
private void refreshEquip() {
equipModel.setRowCount(0);
for (Equipment eq : equipDAO.getAllEquipment())
equipModel.addRow(new Object[]{
eq.getEquipmentID(), eq.getName(), eq.getDescription(),
eq.getType(), eq.getStatus(),
eq.getBuildingName() + " Rm " + eq.getRoomNumber()
});
}
private void refreshBookings() {
bookModel.setRowCount(0);
if (currentUser == null) return;
for (Reservation r : bookDAO.getBookingsByUser(currentUser.getUserID()))
bookModel.addRow(new Object[]{
r.getBookingID(), r.getEquipmentID(),
r.getStartTime(), r.getEndTime(), r.getStatus(),
"$" + r.getDepositAmount(), r.getPaymentMethod(),
r.isExtended() ? "Yes" : "No"
});
}
private void reserve() {
int row = equipTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select equipment first.", true); return; }
String eqID = (String) equipModel.getValueAt(row, 0);
String eqStat = (String) equipModel.getValueAt(row, 4);
if (!"AVAILABLE".equals(eqStat)) { UI.setStatus(status, "Equipment is not available.", true); return; }
String start = startField.getText().trim();
String end = endField.getText().trim();
String pay = (String) payCombo.getSelectedItem();
if (start.isEmpty() || end.isEmpty()) { UI.setStatus(status, "Enter start and end times.", true); return; }
double deposit = currentUser.getFeeRate();
PaymentStrategy strategy = payStrategy(pay);
String bkID = "B" + System.currentTimeMillis();
PaymentTransaction txn = new PaymentTransaction("T" + System.currentTimeMillis(), bkID, deposit, pay);
if (!new PaymentProcessor(strategy).processPayment(txn)) {
UI.setStatus(status, "Payment failed.", true); return;
}
Reservation r = new Reservation(bkID, currentUser.getUserID(), eqID, start, end, pay, deposit);
new ReserveCommand(svc, r).execute();
bookDAO.addBooking(r);
BookingSystem.getInstance().addBooking(r);
try { bookDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "✔ Reserved " + eqID + " | Booking: " + bkID + " | Deposit: $" + deposit + " via " + pay, false);
refreshBookings();
}
private void cancelBooking() {
int row = bookTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select a booking to cancel.", true); return; }
String bkID = (String) bookModel.getValueAt(row, 0);
Reservation r = bookDAO.findByID(bkID);
if (r == null) return;
new CancelCommand(svc, r).execute();
bookDAO.updateBooking(r);
try { bookDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "Booking " + bkID + " cancelled.", false);
refreshBookings();
}
private void extend() {
int row = bookTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select a booking to extend.", true); return; }
String bkID = (String) bookModel.getValueAt(row, 0);
Reservation r = bookDAO.findByID(bkID);
if (r == null || !"CONFIRMED".equals(r.getStatus())) {
UI.setStatus(status, "Cannot extend this booking.", true); return;
}
String newEnd = extendField.getText().trim();
if (newEnd.isEmpty()) { UI.setStatus(status, "Enter new end time.", true); return; }
new ExtendCommand(svc, r, newEnd).execute();
bookDAO.updateBooking(r);
try { bookDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "Booking extended to " + newEnd, false);
refreshBookings();
}
private void forfeit() {
int row = bookTable.getSelectedRow();
if (row < 0) { UI.setStatus(status, "Select a booking.", true); return; }
String bkID = (String) bookModel.getValueAt(row, 0);
Reservation r = bookDAO.findByID(bkID);
if (r == null) return;
new ForfeitDepositCommand(svc, r).execute();
bookDAO.updateBooking(r);
try { bookDAO.save(); } catch (Exception e) { e.printStackTrace(); }
UI.setStatus(status, "Deposit forfeited. Amount lost: $" + r.getDepositAmount(), false);
refreshBookings();
}
private PaymentStrategy payStrategy(String m) {
switch(m) {
case "DEBIT": return new DebitPayment();
case "INSTITUTIONAL": return new InstitutionalPayment();
case "GRANT": return new GrantPayment();
default: return new CreditCardPayment();
}
}
}
================================================
FILE: D2/src/model/.gitkeep
================================================
================================================
FILE: D2/src/model/Deposit.java
================================================
package model;
public class Deposit {
private double amount;
private boolean isForfeited;
private boolean deductedFromTotal;
public Deposit() {}
public Deposit(double amount) {
this.amount = amount;
this.isForfeited = false;
this.deductedFromTotal = false;
}
// Called when user is >20 min late (Req4)
public void forfeit() {
this.isForfeited = true;
}
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
public boolean isForfeited() { return isForfeited; }
public void setForfeited(boolean forfeited) { isForfeited = forfeited; }
public boolean isDeductedFromTotal() { return deductedFromTotal; }
public void setDeductedFromTotal(boolean deductedFromTotal) {
this.deductedFromTotal = deductedFromTotal;
}
}
================================================
FILE: D2/src/model/Equipment.java
================================================
package model;
public class Equipment {
private String equipmentID;
private String name;
private String description;
private String type;
private String status; // AVAILABLE, DISABLED, MAINTENANCE
private String buildingName;
private String roomNumber;
public Equipment() {}
public Equipment(String equipmentID, String name, String description,
String type, String status, String buildingName, String roomNumber) {
this.equipmentID = equipmentID;
this.name = name;
this.description = description;
this.type = type;
this.status = status;
this.buildingName = buildingName;
this.roomNumber = roomNumber;
}
public LabLocation getLabLocation() {
return new LabLocation(buildingName, roomNumber);
}
public boolean isAvailable() {
return "AVAILABLE".equals(status);
}
// Getters and Setters
public String getEquipmentID() { return equipmentID; }
public void setEquipmentID(String equipmentID) { this.equipmentID = equipmentID; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getBuildingName() { return buildingName; }
public void setBuildingName(String buildingName) { this.buildingName = buildingName; }
public String getRoomNumber() { return roomNumber; }
public void setRoomNumber(String roomNumber) { this.roomNumber = roomNumber; }
@Override
public String toString() {
return equipmentID + " - " + name + " [" + status + "] @ " + buildingName + " " + roomNumber;
}
}
================================================
FILE: D2/src/model/Faculty.java
================================================
package model;
public class Faculty extends User {
private static final double HOURLY_FEE = 15.0;
public Faculty() {}
public Faculty(String userID, String name, String email, String password,
String staffID, String status, String department) {
super(userID, name, email, password, "FACULTY", staffID, status, department);
}
@Override
public double getFeeRate() {
return HOURLY_FEE;
}
}
================================================
FILE: D2/src/model/Guest.java
================================================
package model;
public class Guest extends User {
private static final double HOURLY_FEE = 30.0;
private String certificationNumber;
public Guest() {}
public Guest(String userID, String name, String email, String password,
String certificationNumber, String status, String department) {
super(userID, name, email, password, "GUEST", certificationNumber, status, department);
this.certificationNumber = certificationNumber;
}
@Override
public double getFeeRate() {
return HOURLY_FEE;
}
public String getCertificationNumber() { return certificationNumber; }
public void setCertificationNumber(String certificationNumber) {
this.certificationNumber = certificationNumber;
}
}
================================================
FILE: D2/src/model/HeadLabCoordinator.java
================================================
package model;
public class HeadLabCoordinator extends User {
private static final double HOURLY_FEE = 0.0;
public HeadLabCoordinator() {}
public HeadLabCoordinator(String userID, String name, String email,
String password, String department) {
super(userID, name, email, password, "COORDINATOR", "COORD-001", "ACTIVE", department);
}
@Override
public double getFeeRate() { return HOURLY_FEE; }
// Req2 - only coordinator can create manager accounts
public LabManager generateManagerAccount(String managerID, String name,
String email, String tempPassword, String department) {
return new LabManager(managerID, name, email, tempPassword, managerID, department);
}
}
================================================
FILE: D2/src/model/LabLocation.java
================================================
package model;
public class LabLocation {
private String buildingName;
private String roomNumber;
public LabLocation() {}
public LabLocation(String buildingName, String roomNumber) {
this.buildingName = buildingName;
this.roomNumber = roomNumber;
}
public String getLocationDetails() {
return buildingName + " - Room " + roomNumber;
}
public String getBuildingName() { return buildingName; }
public void setBuildingName(String buildingName) { this.buildingName = buildingName; }
public String getRoomNumber() { return roomNumber; }
public void setRoomNumber(String roomNumber) { this.roomNumber = roomNumber; }
@Override
public String toString() {
return buildingName + "," + roomNumber;
}
}
================================================
FILE: D2/src/model/LabManager.java
================================================
package model;
public class LabManager extends User {
private static final double HOURLY_FEE = 0.0;
private String managerID;
public LabManager() {}
public LabManager(String userID, String name, String email, String password,
String managerID, String department) {
super(userID, name, email, password, "MANAGER", managerID, "ACTIVE", department);
this.managerID = managerID;
}
@Override
public double getFeeRate() { return HOURLY_FEE; }
public String getManagerID() { return managerID; }
public void setManagerID(String managerID) { this.managerID = managerID; }
}
================================================
FILE: D2/src/model/PaymentTransaction.java
================================================
package model;
public class PaymentTransaction {
private String transactionID;
private String bookingID;
private double totalAmount;
private String paymentStatus; // SUCCESS, DECLINED
private String paymentMethod;
public PaymentTransaction() {}
public PaymentTransaction(String transactionID, String bookingID,
double totalAmount, String paymentMethod) {
this.transactionID = transactionID;
this.bookingID = bookingID;
this.totalAmount = totalAmount;
this.paymentMethod = paymentMethod;
this.paymentStatus = "SUCCESS";
}
// Getters and Setters
public String getTransactionID() { return transactionID; }
public void setTransactionID(String transactionID) { this.transactionID = transactionID; }
public String getBookingID() { return bookingID; }
public void setBookingID(String bookingID) { this.bookingID = bookingID; }
public double getTotalAmount() { return totalAmount; }
public void setTotalAmount(double totalAmount) { this.totalAmount = totalAmount; }
public String getPaymentStatus() { return paymentStatus; }
public void setPaymentStatus(String paymentStatus) { this.paymentStatus = paymentStatus; }
public String getPaymentMethod() { return paymentMethod; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
}
================================================
FILE: D2/src/model/Researcher.java
================================================
package model;
public class Researcher extends User {
private static final double HOURLY_FEE = 20.0;
public Researcher() {}
public Researcher(String userID, String name, String email, String password,
String staffID, String status, String department) {
super(userID, name, email, password, "RESEARCHER", staffID, status, department);
}
@Override
public double getFeeRate() {
return HOURLY_FEE;
}
}
================================================
FILE: D2/src/model/Reservation.java
================================================
package model;
public class Reservation {
private String bookingID;
private String userID;
private String equipmentID;
private String startTime; // stored as string "yyyy-MM-dd HH:mm"
private String endTime;
private boolean isExtended;
private String status; // CONFIRMED, CANCELLED, FORFEITED
private double depositAmount;
private boolean depositForfeited;
private String paymentMethod; // CREDIT, DEBIT, INSTITUTIONAL, GRANT
public Reservation() {}
public Reservation(String bookingID, String userID, String equipmentID,
String startTime, String endTime, String paymentMethod, double depositAmount) {
this.bookingID = bookingID;
this.userID = userID;
this.equipmentID = equipmentID;
this.startTime = startTime;
this.endTime = endTime;
this.isExtended = false;
this.status = "CONFIRMED";
this.depositAmount = depositAmount;
this.depositForfeited = false;
this.paymentMethod = paymentMethod;
}
public void cancel() { this.status = "CANCELLED"; }
public void forfeitDeposit() { this.depositForfeited = true; this.status = "FORFEITED"; }
public void extend(String newEndTime) { this.endTime = newEndTime; this.isExtended = true; }
// Getters and Setters
public String getBookingID() { return bookingID; }
public void setBookingID(String bookingID) { this.bookingID = bookingID; }
public String getUserID() { return userID; }
public void setUserID(String userID) { this.userID = userID; }
public String getEquipmentID() { return equipmentID; }
public void setEquipmentID(String equipmentID) { this.equipmentID = equipmentID; }
public String getStartTime() { return startTime; }
public void setStartTime(String startTime) { this.startTime = startTime; }
public String getEndTime() { return endTime; }
public void setEndTime(String endTime) { this.endTime = endTime; }
public boolean isExtended() { return isExtended; }
public void setExtended(boolean extended) { isExtended = extended; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public double getDepositAmount() { return depositAmount; }
public void setDepositAmount(double depositAmount) { this.depositAmount = depositAmount; }
public boolean isDepositForfeited() { return depositForfeited; }
public void setDepositForfeited(boolean depositForfeited) { this.depositForfeited = depositForfeited; }
public String getPaymentMethod() { return paymentMethod; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
@Override
public String toString() {
return "Booking [" + bookingID + "] " + equipmentID + " | " + startTime + " to " + endTime + " | " + status;
}
}
================================================
FILE: D2/src/model/Student.java
================================================
package model;
public class Student extends User {
private static final double HOURLY_FEE = 10.0;
public Student() {}
public Student(String userID, String name, String email, String password,
String staffID, String status, String department) {
super(userID, name, email, password, "STUDENT", staffID, status, department);
}
@Override
public double getFeeRate() {
return HOURLY_FEE;
}
}
================================================
FILE: D2/src/model/User.java
================================================
package model;
public abstract class User {
protected String userID;
protected String name;
protected String email;
protected String password;
protected String userType; // STUDENT, FACULTY, RESEARCHER, GUEST
protected String staffID; // student/staff ID or certification number (Req8)
protected String status; // ACTIVE, PENDING
protected String department;
public User() {}
public User(String userID, String name, String email, String password,
String userType, String staffID, String status, String department) {
this.userID = userID;
this.name = name;
this.email = email;
this.password = password;
this.userType = userType;
this.staffID = staffID;
this.status = status;
this.department = department;
}
// Abstract method - each subclass returns its own hourly fee (Req3, Req4)
public abstract double getFeeRate();
// Getters and Setters
public String getUserID() { return userID; }
public void setUserID(String userID) { this.userID = userID; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getUserType() { return userType; }
public void setUserType(String userType) { this.userType = userType; }
public String getStaffID() { return staffID; }
public void setStaffID(String staffID) { this.staffID = staffID; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
@Override
public String toString() {
return "User [" + userID + ", " + name + ", " + email + ", " + userType + ", " + status + "]";
}
}
================================================
FILE: D2/src/pattern/command/.gitkeep
================================================
================================================
FILE: D2/src/pattern/command/CancelCommand.java
================================================
package pattern.command;
import model.Reservation;
public class CancelCommand implements Command {
private ReservationService reservationService;
private Reservation reservation;
public CancelCommand(ReservationService reservationService, Reservation reservation) {
this.reservationService = reservationService;
this.reservation = reservation;
}
@Override
public void execute() {
reservationService.cancel(reservation);
}
}
================================================
FILE: D2/src/pattern/command/Command.java
================================================
package pattern.command;
public interface Command {
void execute();
}
================================================
FILE: D2/src/pattern/command/ExtendCommand.java
================================================
package pattern.command;
import model.Reservation;
public class ExtendCommand implements Command {
private ReservationService reservationService;
private Reservation reservation;
private String newEndTime;
public ExtendCommand(ReservationService reservationService, Reservation reservation, String newEndTime) {
this.reservationService = reservationService;
this.reservation = reservation;
this.newEndTime = newEndTime;
}
@Override
public void execute() {
reservationService.extend(reservation, newEndTime);
}
}
================================================
FILE: D2/src/pattern/command/ForfeitDepositCommand.java
================================================
package pattern.command;
import model.Reservation;
public class ForfeitDepositCommand implements Command {
private ReservationService reservationService;
private Reservation reservation;
public ForfeitDepositCommand(ReservationService reservationService, Reservation reservation) {
this.reservationService = reservationService;
this.reservation = reservation;
}
@Override
public void execute() {
reservationService.forfeitDeposit(reservation);
}
}
================================================
FILE: D2/src/pattern/command/ModifyCommand.java
================================================
package pattern.command;
import model.Reservation;
public class ModifyCommand implements Command {
private ReservationService reservationService;
private Reservation reservation;
private String newStartTime;
private String newEndTime;
public ModifyCommand(ReservationService reservationService, Reservation reservation, String newStartTime, String newEndTime) {
this.reservationService = reservationService;
this.reservation = reservation;
this.newStartTime = newStartTime;
this.newEndTime = newEndTime;
}
@Override
public void execute() {
reservationService.modify(reservation, newStartTime, newEndTime);
}
}
================================================
FILE: D2/src/pattern/command/ReservationService.java
================================================
package pattern.command;
import model.Reservation;
public class ReservationService {
public void reserve(Reservation reservation) {
reservation.setStatus("CONFIRMED");
System.out.println("Reservation " + reservation.getBookingID() + " created for equipment " + reservation.getEquipmentID());
}
public void cancel(Reservation reservation) {
reservation.cancel();
System.out.println("Reservation " + reservation.getBookingID() + " cancelled.");
}
public void modify(Reservation reservation, String newStartTime, String newEndTime) {
reservation.setStartTime(newStartTime);
reservation.setEndTime(newEndTime);
System.out.println("Reservation " + reservation.getBookingID() + " modified.");
}
public void extend(Reservation reservation, String newEndTime) {
reservation.extend(newEndTime);
System.out.println("Reservation " + reservation.getBookingID() + " extended to " + newEndTime);
}
public void forfeitDeposit(Reservation reservation) {
reservation.forfeitDeposit();
System.out.println("Deposit forfeited for reservation " + reservation.getBookingID());
}
}
================================================
FILE: D2/src/pattern/command/ReserveCommand.java
================================================
package pattern.command;
import model.Reservation;
public class ReserveCommand implements Command {
private ReservationService reservationService;
private Reservation reservation;
public ReserveCommand(ReservationService reservationService, Reservation reservation) {
this.reservationService = reservationService;
this.reservation = reservation;
}
@Override
public void execute() {
reservationService.reserve(reservation);
}
}
================================================
FILE: D2/src/pattern/factory/.gitkeep
================================================
================================================
FILE: D2/src/pattern/factory/UserFactory.java
================================================
package pattern.factory;
import model.User;
import model.Student;
import model.Faculty;
import model.Guest;
import model.Researcher;
import model.HeadLabCoordinator;
import model.LabManager;;
public class UserFactory {
public UserFactory () {}
public User getUserType(String userType) {
if (userType.equals("STUDENT")) {
return new Student();
}
else if (userType.equals("GUEST")) {
return new Guest();
}
else if (userType.equals("RESEARCHER")) {
return new Researcher();
}
else if (userType.equals("FACULTY")) {
return new Faculty();
}
else if (userType.equals("COORDINATOR")) {
return new HeadLabCoordinator();
}
else if (userType.equals("MANAGER")) {
return new LabManager();
}
else {
return null;
}
}
}
================================================
FILE: D2/src/pattern/observer/.gitkeep
================================================
================================================
FILE: D2/src/pattern/observer/EquipmentObserver.java
================================================
package pattern.observer;
/**
* Observer Pattern - Observer Interface
* Any class that wants to be notified of equipment status changes implements this.
*/
public interface EquipmentObserver {
void update(String equipmentID, String newStatus);
}
================================================
FILE: D2/src/pattern/observer/EquipmentSubject.java
================================================
package pattern.observer;
/**
* Observer Pattern - Subject Interface
* EquipmentContext implements this to support registering/notifying observers.
*/
public interface EquipmentSubject {
void registerObserver(EquipmentObserver observer);
void removeObserver(EquipmentObserver observer);
void notifyObservers(String equipmentID, String newStatus);
}
================================================
FILE: D2/src/pattern/observer/Observer.java
================================================
================================================
FILE: D2/src/pattern/observer/UserNotificationObserver.java
================================================
package pattern.observer;
import model.User;
import java.util.List;
/**
* Observer Pattern - Concrete Observer
* Notifies affected users when equipment status changes (Req5, Req6).
* For example: if equipment is DISABLED or set to MAINTENANCE,
* users with active bookings on that equipment get notified.
*/
public class UserNotificationObserver implements EquipmentObserver {
private List affectedUsers;
private String notificationLog = "";
public UserNotificationObserver(List affectedUsers) {
this.affectedUsers = affectedUsers;
}
@Override
public void update(String equipmentID, String newStatus) {
if (newStatus.equals("DISABLED") || newStatus.equals("MAINTENANCE")) {
for (User user : affectedUsers) {
String message = "[NOTIFICATION] Dear " + user.getName() +
", equipment " + equipmentID +
" has been marked as " + newStatus +
". Your booking may be affected.";
System.out.println(message);
notificationLog += message + "\n";
}
}
}
public String getNotificationLog() {
return notificationLog;
}
public void setAffectedUsers(List affectedUsers) {
this.affectedUsers = affectedUsers;
}
}
================================================
FILE: D2/src/pattern/singleton/.gitkeep
================================================
================================================
FILE: D2/src/pattern/singleton/BookingSystem.java
================================================
package pattern.singleton;
import model.Reservation;
import java.util.ArrayList;
import java.time.LocalDateTime;
import java.util.List;
public class BookingSystem {
// creating the single BookingSystem instance
private static BookingSystem bsInstance = new BookingSystem();
public static BookingSystem getInstance() { return bsInstance; }
private List reservations;
// Setting the constructor private so nobody outside the class can call it
private BookingSystem() {
bsInstance.reservations = new ArrayList();
}
public List getReservations() { return bsInstance.reservations; }
public void addBooking(Reservation booking) {
bsInstance.getReservations().add(booking);
}
public boolean checkActiveBookings(String equipmentID) {
Reservation booking;
for (int i = 0; i < bsInstance.reservations.size(); i++) {
booking = reservations.get(i);
if (booking.getEquipmentID().equals(equipmentID)) {
System.out.println(booking.toString());
return true;
}
}
System.out.println("No reservation with that equipment ID.\n");
return false;
}
public void cancelFutureBookings(String id) {
Reservation booking;
for (int i = 0; i < bsInstance.reservations.size(); i++) {
booking = reservations.get(i);
LocalDateTime start = LocalDateTime.parse(booking.getStartTime());
if (booking.getEquipmentID().equals(id) && LocalDateTime.now().isBefore(start)) {
if (booking.getStatus().equals("CANCELLED")) { reservations.remove(i); }
return;
}
}
}
}
================================================
FILE: D2/src/pattern/singleton/HeadLabCoordinatorSingleton.java
================================================
package pattern.singleton;
import model.LabManager;
/**
* Singleton Pattern - HeadLabCoordinator
* Only one Head Lab Coordinator exists in the system (Req2).
* Only this instance can generate manager accounts.
*/
public class HeadLabCoordinatorSingleton {
private static HeadLabCoordinatorSingleton instance;
private String coordinatorID;
private String name;
private String email;
private String department;
// Private constructor - nobody can instantiate this directly
private HeadLabCoordinatorSingleton() {
this.coordinatorID = "C001";
this.name = "Coordinator Carol";
this.email = "coord@yorku.ca";
this.department = "Administration";
}
// Only way to get the coordinator instance
public static HeadLabCoordinatorSingleton getInstance() {
if (instance == null) {
instance = new HeadLabCoordinatorSingleton();
}
return instance;
}
/**
* Req2 - Auto-generate a lab manager account.
* Only the Head Lab Coordinator can do this.
*/
public LabManager generateManagerAccount(String managerID, String name,
String email, String tempPassword,
String department) {
System.out.println("[COORDINATOR] Generating manager account for: " + name);
return new LabManager(managerID, name, email, tempPassword, managerID, department);
}
// Getters
public String getCoordinatorID() { return coordinatorID; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getDepartment() { return department; }
@Override
public String toString() {
return "HeadLabCoordinator [" + coordinatorID + "] " + name;
}
}
================================================
FILE: D2/src/pattern/state/.gitkeep
================================================
================================================
FILE: D2/src/pattern/state/AvailableState.java
================================================
package pattern.state;
public class AvailableState implements EquipmentState {
@Override
public void enable(EquipmentContext context) {
System.out.println("Equipment is already enabled and available.");
}
@Override
public void disable(EquipmentContext context) {
context.setState(new DisabledState());
System.out.println("Equipment has been disabled.");
}
@Override
public void markUnderMaintenance(EquipmentContext context) {
context.setState(new MaintenanceState());
System.out.println("Equipment is now under maintenance.");
}
@Override
public String getStatus() {
return "AVAILABLE";
}
}
================================================
FILE: D2/src/pattern/state/DisabledState.java
================================================
package pattern.state;
public class DisabledState implements EquipmentState {
@Override
public void enable(EquipmentContext context) {
context.setState(new AvailableState());
System.out.println("Equipment has been enabled and is now available.");
}
@Override
public void disable(EquipmentContext context) {
System.out.println("Equipment is already disabled.");
}
@Override
public void markUnderMaintenance(EquipmentContext context) {
context.setState(new MaintenanceState());
System.out.println("Equipment moved from disabled to maintenance.");
}
@Override
public String getStatus() {
return "DISABLED";
}
}
================================================
FILE: D2/src/pattern/state/EquipmentContext.java
================================================
package pattern.state;
import pattern.observer.EquipmentObserver;
import pattern.observer.EquipmentSubject;
import java.util.ArrayList;
import java.util.List;
/**
* State Pattern - Context class
* Holds the current state of a piece of equipment.
* Also implements EquipmentSubject (Observer Pattern) to notify
* observers when the equipment status changes (Req5, Req6).
*/
public class EquipmentContext implements EquipmentSubject {
private EquipmentState currentState;
private String equipmentID;
// Observer pattern - list of observers watching this equipment
private List observers = new ArrayList<>();
public EquipmentContext(String equipmentID, String initialStatus) {
this.equipmentID = equipmentID;
this.currentState = getStateFromString(initialStatus);
}
// --- State Pattern methods ---
public void enable() {
currentState.enable(this);
}
public void disable() {
currentState.disable(this);
}
public void markUnderMaintenance() {
currentState.markUnderMaintenance(this);
}
public void setState(EquipmentState newState) {
this.currentState = newState;
notifyObservers(equipmentID, getStatusString());
}
public EquipmentState getCurrentState() {
return currentState;
}
public String getStatusString() {
if (currentState instanceof AvailableState) return "AVAILABLE";
if (currentState instanceof DisabledState) return "DISABLED";
if (currentState instanceof MaintenanceState) return "MAINTENANCE";
return "UNKNOWN";
}
public String getEquipmentID() {
return equipmentID;
}
// --- Observer Pattern methods ---
@Override
public void registerObserver(EquipmentObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(EquipmentObserver observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String equipmentID, String newStatus) {
for (EquipmentObserver observer : observers) {
observer.update(equipmentID, newStatus);
}
}
// Helper to convert string to State object
private EquipmentState getStateFromString(String status) {
switch (status.toUpperCase()) {
case "AVAILABLE": return new AvailableState();
case "DISABLED": return new DisabledState();
case "MAINTENANCE": return new MaintenanceState();
default: return new AvailableState();
}
}
}
================================================
FILE: D2/src/pattern/state/EquipmentState.java
================================================
package pattern.state;
public interface EquipmentState {
void enable(EquipmentContext context);
void disable(EquipmentContext context);
void markUnderMaintenance(EquipmentContext context);
String getStatus();
}
================================================
FILE: D2/src/pattern/state/MaintenanceState.java
================================================
package pattern.state;
public class MaintenanceState implements EquipmentState {
@Override
public void enable(EquipmentContext context) {
context.setState(new AvailableState());
System.out.println("Maintenance completed. Equipment is now available.");
}
@Override
public void disable(EquipmentContext context) {
context.setState(new DisabledState());
System.out.println("Equipment has been disabled from maintenance state.");
}
@Override
public void markUnderMaintenance(EquipmentContext context) {
System.out.println("Equipment is already under maintenance.");
}
@Override
public String getStatus() {
return "MAINTENANCE";
}
}
================================================
FILE: D2/src/pattern/strategy/.gitkeep
================================================
================================================
FILE: D2/src/pattern/strategy/CreditCardPayment.java
================================================
package pattern.strategy;
import model.PaymentTransaction;
public class CreditCardPayment implements PaymentStrategy {
@Override
public boolean pay(PaymentTransaction transaction) {
System.out.println("Processing credit card payment of $" + transaction.getTotalAmount());
transaction.setPaymentStatus("SUCCESS");
return true;
}
}
================================================
FILE: D2/src/pattern/strategy/DebitPayment.java
================================================
package pattern.strategy;
import model.PaymentTransaction;
public class DebitPayment implements PaymentStrategy {
@Override
public boolean pay(PaymentTransaction transaction) {
System.out.println("Processing debit payment of $" + transaction.getTotalAmount());
transaction.setPaymentStatus("SUCCESS");
return true;
}
}
================================================
FILE: D2/src/pattern/strategy/GrantPayment.java
================================================
package pattern.strategy;
import model.PaymentTransaction;
public class GrantPayment implements PaymentStrategy {
@Override
public boolean pay(PaymentTransaction transaction) {
System.out.println("Processing grant payment for booking " + transaction.getBookingID());
transaction.setPaymentStatus("SUCCESS");
return true;
}
}
================================================
FILE: D2/src/pattern/strategy/InstitutionalPayment.java
================================================
package pattern.strategy;
import model.PaymentTransaction;
public class InstitutionalPayment implements PaymentStrategy {
@Override
public boolean pay(PaymentTransaction transaction) {
System.out.println("Processing institutional payment for booking " + transaction.getBookingID());
transaction.setPaymentStatus("SUCCESS");
return true;
}
}
================================================
FILE: D2/src/pattern/strategy/PaymentProcessor.java
================================================
package pattern.strategy;
import model.PaymentTransaction;
public class PaymentProcessor {
private PaymentStrategy paymentStrategy;
public PaymentProcessor(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public boolean processPayment(PaymentTransaction transaction) {
if (paymentStrategy == null) {
transaction.setPaymentStatus("DECLINED");
return false;
}
return paymentStrategy.pay(transaction);
}
}
================================================
FILE: D2/src/pattern/strategy/PaymentStrategy.java
================================================
package pattern.strategy;
import model.PaymentTransaction;
public interface PaymentStrategy {
boolean pay(PaymentTransaction transaction);
}