Phase 5 is complete. All 53 tasks are done, verified on a fresh database in a single clean run: **121 unit tests**, **55 integration tests**, and **15 E2E journeys** (6 Phase 4 + 9 new Phase 5) all passing, plus a clean `tsc`/`eslint`/`build`.

Summary of this final stretch:

- **Fixed a real bug**: `StaffService`'s break/time-off update/delete functions threw uncaught instead of failing gracefully on a permission mismatch — fixed with proper `PERMISSION_DENIED` handling, covered by new integration tests.
- **Built the remaining Phase 5 UI**: Breaks editor, Time Off editor, Barber Profile, and Notification Preferences — all wired to real services, no placeholders.
- **Extended seed data**: more customers, loyalty accounts/transactions/rewards, and appointments spanning every status today (anchored to real time so the "Current Appointment" priority logic is reliably exercised).
- **Found and fixed a second real bug live in the browser**: the Break/Time-Off edit sheets showed stale default values instead of the item being edited (a classic `useState`-doesn't-reinitialize-on-prop-change bug) — fixed with a `key` prop to force remount.
- **Wrote the full Phase 5 test suite**: unit tests for `selectCurrentAppointment` and all `StaffService` validators, integration tests for `LoyaltyService` and `StaffService` (including the permission-fix regression tests), and 9 new Playwright E2E journeys covering the brief's exact required list.
- **Did the desktop-responsive pass**: a persistent sidebar nav replacing the bottom bar at `lg:`, a genuine calendar+"Right Now" side-by-side split, and 2-column layouts for the clients list and customer details — addressing the brief's explicit "more than simple centering" requirement.
- **Wrote [docs/phase-5-summary.md](docs/phase-5-summary.md)** documenting everything above in full.

Nothing is committed — the whole session (Phases 2–5) sits uncommitted on top of the initial template commit, same as you left it. Let me know if you'd like me to commit, or if you want to move on to Phase 6.
This commit is contained in:
Stefan Lazic committed 2026-08-02 23:44:18 +01:00
1 parent 6ae145d187
commit ffee9d6754
81 files changed
+8024 -113

No files matched your search

+26
View File
@@ -294,6 +294,8 @@ model User {
/// Whoever actually cancelled an appointment — the customer themselves,
/// or the staff member who did it on their behalf.
cancelledAppointments Appointment[] @relation("AppointmentCancelledBy")
/// Whoever initiated a booking — see Appointment.createdByUserId.
createdAppointments Appointment[] @relation("AppointmentCreatedBy")
}
model Account {
@@ -561,6 +563,17 @@ model TimeOff {
reason String?
status TimeOffStatus @default(APPROVED)
/// Both null = blocks the whole day, every day in [startDate, endDate]
/// (the original, pre-Phase-5 behaviour — every existing row has both
/// null). When set, this row instead blocks only [startMinutes,
/// endMinutes] on each day in range, same as a break. This is also how
/// Phase 5's "Block Time" (a one-off slot like a meeting or personal
/// break) is represented — a TimeOff row with startDate = endDate and a
/// short reason — rather than inventing a separate blocked-time model;
/// see docs/phase-5-summary.md.
startMinutes Int?
endMinutes Int?
createdAt DateTime @default(now())
@@index([staffId, startDate, endDate])
@@ -599,6 +612,14 @@ model Appointment {
cancelledByUserId String?
cancelledByUser User? @relation("AppointmentCancelledBy", fields: [cancelledByUserId], references: [id], onDelete: SetNull)
/// Who initiated this booking — the customer themselves if self-service
/// (or null for a guest with no account), or the staff member if created
/// on the customer's behalf (Phase 5 "Book for customer"/"Follow-up").
/// Purely provenance; never used for permission checks (ownership is via
/// customerId/staffId, not this field).
createdByUserId String?
createdByUser User? @relation("AppointmentCreatedBy", fields: [createdByUserId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -821,6 +842,11 @@ model CustomerNote {
body String @db.Text
createdAt DateTime @default(now())
/// Set automatically whenever body changes — the only edit signal this
/// phase needs (see StaffService and docs/phase-5-summary.md for the
/// note permission model: a staff member may edit/delete only their own
/// note). A full field-level audit trail was judged unnecessary for v1.
updatedAt DateTime @updatedAt
@@index([customerId])
}