Doctor Schedule & True Slot Availability
Problem
Patient booking uses a free-form TimePicker with no awareness of:- Which time slots are actually within clinic hours
- Whether a slot is already taken by another patient
- Whether a specific doctor works that day/time
What We’re Building
doctor_schedulestable — per-doctor weekly availabilityGET /available-slotsAPI — returns genuinely open slots for a given date + doctor- Patient booking UI — slot grid replacing the TimePicker
- Settings: Doctor Schedule — weekly picker per doctor in Settings > Staff
Schema: doctor_schedules
New table in app schema:
server/src/schema/ as doctor_schedules.ts, exported from index.ts.
Schema-ensure: ensureAppointmentsSchema creates table if missing.
API: Available Slots
Endpoint
GET /api/v1/protected/appointments/available-slots
Query Params
| Param | Required | Description |
|---|---|---|
date | yes | YYYY-MM-DD |
doctorId | no | filter by doctor; if omitted, returns union of all active doctors |
Logic
- Parse
dayOfWeekfromdate - Fetch clinic operating hours for that day → if
isClosed, return{ slots: [], clinicClosed: true } - Fetch
doctor_schedulesfor that doctor+day → ifis_off, return{ slots: [], doctorOff: true } - If no doctor schedule row exists, fall back to clinic hours (doctor has no schedule configured)
- Effective window = intersection of clinic hours and doctor hours
- Generate 30-minute slots within effective window
- Fetch existing confirmed/pending appointments for that doctor+date
- Remove booked slots
- Remove past slots if date is today (compare to PKT now)
- Return
{ slots: ["09:00","09:30",...], clinicPhone: "...", clinicClosed: false }
Response
Auth
RequiresX-Clinic-Id header (standard clinic-context middleware). Patient portal uses the existing auth token.
Patient Booking UI Changes
File:ui/src/components/dashboards/PatientPortal.tsx — AppointmentsView
Replace the <TimePicker> with a <SlotPicker> component:
SlotPicker Component (ui/src/components/ui/slot-picker.tsx)
- Fetches
/available-slots?date=&doctorId=when date or doctor changes - Shows loading skeleton while fetching
- Renders slots as a grid of buttons (4 per row, 30-min increments)
- Grey/disabled: past slots, booked slots
- If
clinicClosed === trueorslots.length === 0: show “Clinic is closed on this day. Call us: [phone]” - Clinic phone sourced from the clinic object already loaded in portal
Doctor Selection
- If clinic has multiple active doctors: show a “Doctor” dropdown (optional — patient can leave it “Any”)
- “Any” = fetch slots for all doctors and show union (slot is available if any doctor has it free)
Settings: Doctor Schedule
File:ui/src/components/settings/StaffManagement.tsx (or equivalent staff settings page)
Add an “Availability” section per staff member who has the doctor role:
- Weekly grid: Mon–Sun rows, each row has: Toggle (working/off) + time range (start–end)
- Default: inherits clinic hours if no schedule set
- Save calls
PUT /api/v1/protected/doctor-schedules/:doctorId
Routes
GET /api/v1/protected/doctor-schedules— fetch all schedules for clinicPUT /api/v1/protected/doctor-schedules/:doctorId— upsert full weekly schedule for a doctor- Body:
{ schedule: [{ dayOfWeek, startTime, endTime, isOff }] }
Permissions
No new permissions needed. Doctor schedule management sits under existing staff management permission. Available-slots endpoint is readable by any authenticated clinic user.Error Handling
- No rows in
doctor_schedulesfor a doctor → fall back to clinic hours (graceful, no error) ensureAppointmentsSchemacreates the table on first hit (like all other schema-ensure calls)- Invalid date → 400
- doctorId not in clinic → 403
Out of Scope
- Break times / lunch slots (can be added later)
- Multi-location schedules
- Recurring exceptions (holidays)

