Loading demo…

What I would do differently

The demo above is a faithful port, defects included: where the 2023 code got something wrong, the demo gets it wrong the same way. What follows is the audit, linked into the original repository, unchanged since submission.

Authentication was a database connection

The login screen has no concept of a user. onLoginClick calls JDBC.java:17–21, which hands the typed username and password to DriverManager.getConnection and returns the string "Connection successful!"; HelloController.java:57–67 gates entry on comparing that string, calling openConnection twice per click — once for a log line, once for the condition. A real credential check against the users table exists at UsersQuery.java:13–27, called from nowhere. Every attempt — username, password in cleartext, timestamp, connection result — is appended to the Windows-separator path hardcoded at HelloController.java:32, and the input box at hello-view.fxml:20–24 is a TextField, not a PasswordField, so the password is echoed on screen as well as written to disk. The right shape is a service account for the connection, credentials checked against a hashed column, and a log that never records the secret.

Identity kept in static fields

Contacts.contactId at Contacts.java:4–10 and Users.userId at Users.java:3–9 are static yet assigned through this in the constructor, so every instance shares one id and building the next contact rewrites the previous one’s. The same instinct runs through the controllers. selectedCustomer is a public static written only by the mouse handler at scheduleViewController.java:219–234, while Add Appointment guards on the table’s own selection model instead — so a keyboard-selected row passes the guard and then throws a NullPointerException at createAppointmentController.java:144. And scheduleViewController.java:291–300 clears the table and re-runs the query without calling setItems again; it refreshes only because the ObservableList handed to the TableView is the same static list the query mutates in place. Ids belong on instances, selection belongs to the view, and that aliasing should have been an explicit rebind.

Time zones converted zero times, or twice

The brief asked for appointments validated against Eastern office hours. At createAppointmentController.java:66–95 the UTC conversion is commented out and the check reads the raw picker hour against 8..22, enforcing whatever zone the machine happens to be in — and > 22 lets 22:59 through despite an alert that says 8am–10pm. In the other direction, Appointments.java:48–63 builds now with toUTC, applies toUTC to both bounds again, and converts the appointment start on only one side of the &&, so the fifteen-minute reminder window is offset by the zone and compares two representations of the same instant. Store instants in UTC, convert once at the boundary, and validate in the zone the rule is written in.

Guards that cannot fire, alerts that do not match

The new-customer guard at createCustomerController.java:35–43 compares the injected @FXML control references to null rather than their contents; after injection all six are non-null, so a blank form falls through to the insert. The overlap branch at createAppointmentController.java:96–100 is real logic wired to copy-pasted copy: it detects a customer double-booking and tells the user the meeting is outside work hours. hasAppointment at Customers.java:118–123 indexes the customer list by id instead of position, discards the result and returns false unconditionally, so its only observable behaviour is the IndexOutOfBoundsException it can throw. Validation should read values, and each rejection should own its message.

Unfinished work that shipped anyway

setTables() calls three setters; two of them, at reportController.java:83–89, are empty bodies, and three of the four declared column groups are referenced nowhere — so two report tables show an empty-table placeholder forever. The report asks for type "Follow-Up" at reportController.java:145 while the picker stores "Follow-up", latent only because MySQL’s default collation compares case-insensitively, and live the moment that collation changes. The modify-appointment screen is headed “Create Appointment” at modifyAppointmentView.fxml:16; both customer screens misspell the word, starting at createCustomerView.fxml:19; navigating home blanks the window title at NavigationUtil.java:14–27; and debug printing remains in the build, two lines of it labelled with line numbers they had already drifted past at modifyAppointmentsController.java:131–132. None of these are hard problems. They are the residue of finishing against a deadline with no review step, which is the part that actually changed.

Source
demos/sw2-scheduler
Isolation
inline