- Added new icons and improved error messaging for better user feedback.
- Refactored state management to include baseline comparison for dirty state detection.
- Introduced functionality to copy shifts across all days and reset to baseline.
- Updated UI layout for better responsiveness and usability.
- Enhanced tests to cover new features and ensure proper functionality.
refactor: update ClinicAppointmentSettingsPage to use PageHeader component
- Replaced BackButton with PageHeader for a more consistent header layout.
- Simplified the structure of the appointment settings page for better readability.
test: improve ResourceDetailPage tests for shift management
- Updated tests to reflect changes in shift display and error handling.
- Added tests for new features including the reset functionality and copying shifts.
style: add styles for weekly shift layout in ResourceWorkingHoursPanel
- Introduced new CSS classes for better layout and responsiveness of the weekly shift display.
- Ensured styles are consistent with the overall design system.
A resource carries its own working hours and holidays in the resource-first
model, so it belongs on the same settings page as a doctor's schedule rather
than on a page of its own. The page now has a scope switch — doctors or
resources — with the per-item tab bar below it, and both scopes reuse the
panels that already existed: ScheduleSection for a doctor, the working-hours
and exceptions panels for a resource. The selection lives in the query
string, so back and refresh return to the same tab.
The screenshot of the finished tab caught two real defects, both fixed here:
Dates in the resource panels and the holidays page read as year 57932.
formatDate already multiplies seconds by 1000, and five call sites passed
`x * 1000` on top of it. This predates the tab — the code was inherited from
the old calendar page — but it was invisible until a two-week preview was put
on screen.
The working-hours panel still told the user their hours were intersected with
the branch's. Branches are gone; the shift is the only source now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The holiday model was already right — national holidays global, a per-tenant
override in both directions, per-doctor and per-resource exceptions — but
nothing could create a national holiday. The only writer was an import
command, so the calendar the whole product inherits from had no owner.
Three admin-only routes give it one. POST upserts, because `date` is unique
and re-sending a day should rename it rather than surface a raw database
error; PATCH takes only the title, because moving a date means a different
holiday. The system admin has no work environment, so the list endpoint now
returns the calendar with an empty `overrides` for that role instead of the
403 `pair()` would raise — the person who maintains the calendar has to be
able to read it.
Both holiday tabs — the doctor's and the resource's — now open with the
official calendar above their own exceptions, from one shared card rather
than two copies that would drift. Each row can be opted out of with a single
click, which is the existing holiday-override endpoint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Branches and rooms are not part of the resource-first product: a room is a
resource like any other, and the only thing the branch pages still managed —
opening hours — duplicated the resource's own shift.
What could not go is the address. Every appointment carries address_id (75 of
75 rows), the public booking site reads /clinic-pro/doctor-address/{id}, and a
resource derives its tenant pair from the address it belongs to. So
DoctorAddress stays as an invisible anchor with no page and no menu entry, and
GET /api/v1/addresses replaces GET /api/v1/branches for the forms that still
need to say "where".
BranchResolver was likewise not a branch feature. doctor_addresses is a global
table, so TenantFilter does not cover it and eight callers across booking,
availability, pricing and the catalog went through this resolver to avoid
leaking another clinic's address. It moved to Doctor\Service\AddressResolver
rather than dying with the domain.
The availability engine loses one layer: a resource's real hours were the
branch hours intersected with its shift, and are now the shift alone. That is
the single behavioural change, and the three tests that asserted the old
contract are replaced by one that states the new one.
Rooms already had a resource row each; the migration drops only the bridge
back to `rooms`, and drops it before the table — that foreign key is ON DELETE
CASCADE and the other order would take the resources, and their appointments,
with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In the resource-first model a resource is the unit of capacity, so its
working hours, holidays, services, skills and categories belong to it — not
scattered across a list page's modals plus a separate calendar page.
/admin/resources/{uuid} now carries six tabs and the active tab lives in the
query string, so back and refresh land on the same view. The old
/calendar URL redirects to ?tab=hours instead of 404ing.
The skills and services modal bodies became panels the tab renders directly;
the modals are now thin wrappers, so the list page keeps working unchanged
and there is still one implementation of each editor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>