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); }