9.5 KiB
9.5 KiB
Frontend Phase 8 (f8-b9) — Booking detail, sessions & nurse EVV — report
Date: 2026-07-10 · Lane: frontend · Consumes: bookings-evv.md (b9) · Unlocks: f9 (checkout/pay), f13 (reviews & patient records)
What was built
The post-payment engagement — the hinge between "I asked for a nurse" and "a nurse is delivering care."
Data layer — a new services/bookings domain
The sibling of services/bookingRequests (b8), not a rename — a distinct b9 contract, distinct
routes (/api/v1/bookings/* + /api/v1/booking_sessions/*), distinct shapes. Same services/{domain}
shape as every other domain:
types.ts— derived 1:1 from the b9 swagger (camelCase):BookingDetailDto,BookingSessionDto(BookingSessionSummaryDto),VisitVerificationDto,CareInstructionsDto,BookingListItemDto,BookingSessionListItemDto,CheckInVisitInput/CheckOutVisitInput, theBookingsApiseam, the three enum unions (BookingStatus/BookingSessionStatus/VisitVerificationStatus), and pure helpers (isBookingConfirmedOrBeyond,isBookingTerminalBranch,bookingTimelineActiveIndex,BOOKING_TIMELINE_ORDER).keys.ts—bookingDetail(id),bookingSessions(id)(alias ofbookingDetail— sessions are embedded),today(params),sessionEvv(id),careInstructions(id),list(params).apis/— realclientApi(maps the routes 1:1),mockApi(the seeded state machine, primary),serverApi.getBookingDetail(the RSC-prefetch seam for the real path), and a config-selectingindex.evv/locationProvider.ts— theILocationProviderGPS seam (realnavigator.geolocationvs a canned mock;getCurrentPosition()resolvesnullon denial, never rejects).hooks/(one per file):useBookingDetail,useBookingSessions(aselectover the detail query, no second fetch),useBookingList,useTodaySessions,useSessionEvv,useCareInstructions(enabled-gated),useCheckInVisit,useCheckOutVisit(both invalidate detail+sessionEvv+today+list on success).
Shared composites — src/components/booking/ (each with a co-located *.test.tsx)
BookingDetailView— the both-roles smart container (role-conditioned EVV + gated care).BookingStatusTimeline— the server-truth 7-status timeline over the f0StepperHeader+ status chip.SessionList→SessionCard— per-session Shamsi schedule, status chip, EVV CTA, elapsed/payout.EvvStatusBanner— advisory banner (in-range success / out-of-range warning / no-gps neutral).CareInstructionsCard— the decrypted clinical read (conditions/meds/allergies/instructions/emergency).BookingMoneySummary— gross / commission (کارمزد) / payout, display-only via the money util.useEvvController— GPS-capture + check-in/out orchestration (one instance per surface, per-session busy).format.ts(clock/duration) +statusKind.ts(status → StatusChip kind) helpers.
Screens
- Customer:
/bookings(رزروها list) →/bookings/[id](detail, customer view: timeline + sessions + money; the care record shows the "visible to your nurse only" affordance and the query never fires). - Nurse:
/nurse/visits(ویزیت امروز — today's sessions with inline EVV check-in/out) →/nurse/visits/[id](detail, nurse view: EVV controls + the gated care card).
Cross-cutting
- i18n
bookingnamespace extended (both locales, key-synced):bstatus_*,sstatus_*,evv_*(banner variants + CTAs + GPS copy),care_*(+ the customer lock copy),money_*,list_*, dispute note. - 8 new registry icons (
check_in/check_out/gps/schedule/clinical/medication/emergency/lock) and one new token--bal-secondary-soft(the نمای پرستار chip / EVV affordance), both schemes.
What is now testable, and exactly how
Run npm run dev (mock is primary — no backend needed). The b9 endpoints are also live if you flip the flag.
- Confirmed booking (customer): open the رزروها tab → the list shows the two seeded bookings; open one
→ status timeline at
confirmed, the session schedule (booking #5002 shows exactly one session; #5001 shows 3), and the money summary in Toman. Toggle/en↔/fa→ strings +dirflip; the timeline reads RTL. - Care gate: open a booking as the nurse (
/nurse/visits→ a session → view booking) → the care-instructions card is visible (conditions/meds/allergies/instructions/emergency). As the customer, the card is absent and the Network tab shows the care request was never made (proven by theBookingDetailViewtest too). - Nurse check-in: on
/nurse/visits(or in the nurse booking detail), tap «ثبت ورود (EVV)» → "در حال دریافت موقعیت…" → the «ورود ثبت شد … موقعیت تایید شد (EVV)» banner; the session chip →in_progress; the timeline →in_progress. SetNEXT_PUBLIC_EVV_MOCK_GPS=out_of_range→ the advisory «موقعیت خارج از محدوده (در حال بررسی)» banner and the check-in still succeeds.=denied→ the nurse still checks in (no block; advisory toast + no-gps banner). - Nurse check-out: tap «ثبت خروج (EVV)» → the session chip →
completedwith elapsed duration; for the single-visit booking (#5002) the timeline advances tocompleted+ the dispute-window note appears — all from the server response, no client-side step jump. - Caching: in React Query Devtools, an EVV mutation invalidates
bookingDetail/sessionEvv/today/listand the UI re-renders from the refetch; revisiting withinstaleTimedoes not refetch. npm run checkgreen ·npm run test:cigreen (195 tests, +22).
What is mocked / waiting on a real service
services/bookings— mock-primary (USE_BOOKINGS_MOCK=true). A booking only exists afterbookings/convertruns on a paid request, and both upstreams (bookingRequestsmock, card capture b10) aren't real client-side yet, so a realbookings/listreturns nothing. The mock seeds confirmed bookings + sessions + care + the EVV state machine. RealbookingsClientApimaps the routes 1:1; the swap is one flag (see mocks-registry).serverApi.getBookingDetailis ready for the RSC prefetch on the real path.ILocationProvider(NEXT_PUBLIC_EVV_MOCK_GPS) — GPS capture seam; the real path isnavigator.geolocation. Server-side address-match math stays behind the backend geocoding seam.- Both are recorded in mocks-registry.md.
Contract consumed + gaps filed
- Consumed: bookings-evv.md + the b9 swagger shapes — types derive 1:1. No shape was guessed.
- Filed: REQ-015 — confirm the booking/session/EVV enum string codes (bare
stringin swagger) match the client unions, and thecheckInAddressMatchtri-state (true/false/null) so the banner can distinguish an advisory mismatch from "no GPS captured." Low-risk (mock-primary now); worth locking before f9/f13 reuse the shapes.
Deliberate design decisions (non-obvious)
- Two-stage disclosure is a UI gate, not just a server check.
useCareInstructionsisenabledonly for the assigned-nurse view on aconfirmed+ booking; the customer/unassigned viewer never fires the request (a 403/404 is treated as a defect path). TheBookingDetailViewtest asserts the customer never callsgetCareInstructionsand the nurse does. - EVV mismatch/denial is advisory, never a block. Out-of-range check-in succeeds with a warning-tokened banner (never the error token); GPS denial still submits. Check-out is never gated on the match.
- Server-truth timeline + display-only money. The timeline renders
BookingDetailDto.statusexactly (no client step advance); money is rendered as-sent (no sum/derive/re-split);payoutEligibleAtis never recomputed. Sessions are embedded in the detail (no standalone list endpoint) —useBookingSessionsis aselectover the one detail query, so invalidatingbookingDetail(id)refreshes both. ILocationProvideris the single new client seam; theNEXT_PUBLIC_EVV_MOCK_GPSdefault isin_rangewhile mock-primary so the happy path is demoable without a device (real GPS would never fall near the seeded Tehran address).
Follow-ups for later phases
- f9 (checkout/pay): the money summary here shows the confirmed split only; the tax (مالیات) line,
escrow notice, and invoice are the checkout surface — b9's
BookingDetailDtohas no tax field (flagged; f9 owns it). The C5 accept CTA still lands on/bookings/checkout?request_id=…(f7 stub). - f13 (reviews & records): the E3 visit-note authoring (bottom half) and the full E2 patient-record
viewer are deferred here; the booking-detail/EVV/care pattern (timeline + sessions + gated care + EVV
banner) is the template they extend. The customer-side care-details authoring (
submit_care_instructions) write form is also f13 — f8 only reads the gated record. - f15 (admin): the EVV-review queue (mismatch / no-show worklist) is the admin console; f8 raises no alerts client-side (no-show detection is a server job).
- Swap to real: flip
USE_BOOKINGS_MOCK=falseoncebookings/convertis reachable client-side (b10 card capture) —bookingsClientApi+bookingsServerApiare wired; no hook/component change.