29 KiB
Frontend Phase 8 — Booking detail, sessions & nurse EVV
Mission: turn an accepted-and-paid request into a living engagement on screen. Build the booking-detail view both actors share — a server-truth status timeline (
pending_payment → confirmed → in_progress → completed → disputed/closed/cancelled) and the per-visit session schedule — plus the nurse's day-one operational surface: EVV check-in/out (capture GPS, post it, show the "ورود ثبت شد · موقعیت تایید شد (EVV)" banner) and the care-instructions read that is unlocked only post-confirmation to the assigned nurse. This is the screen where the trust promise (proof of service, two-stage clinical disclosure, escrow-then-release) becomes visible, and it is the launch pad for checkout (f9) and reviews/records (f13).Track: frontend · Depends on:
frontend-phase-7-b8.md(request flow + booking lists/status tracker) and the b9 contract (bookings-evv.md) · Unlocks: checkout & payment (frontend-phase-9-b10.md), reviews & patient records (frontend-phase-13-b14.md) Before you start, read../_shared/agent-operating-rules.md. It is not optional.
1. Context — where this sits
We are at f8 of the frontend chain. The customer can now search, open a nurse, send a request, and
watch it move pending_nurse_response → accepted_awaiting_payment (built in f7); the nurse has an inbox
to accept/reject. The backend's b9 phase has just landed the booking phase: a request that was
accepted and paid converts to a bookings row with booking_sessions, encrypted
booking_care_instructions, and per-session visit_verifications for EVV. This phase puts a face on all
of that. It is the hinge between "I asked for a nurse" and "a nurse is actually delivering care" — the
booking detail is where the customer watches the engagement progress and the nurse does their job.
Two product truths drive every decision here, and both are non-negotiable:
- Two-stage clinical disclosure (Principle 6): full care instructions are encrypted and revealed only after confirmation, only to the assigned nurse + admin. The UI must gate this, never leak it.
- EVV is proof of service (06-evv-and-service-delivery.md): the nurse clocks in/out per session with GPS; a location mismatch is advisory (a banner, never a block); EVV check-out is what eventually makes a session payout-eligible (after the dispute window — that gating lives server-side; we just render its result).
What already exists (do not rebuild) — link and extend, never re-create:
- f0 foundations (
frontend-phase-0.md): the three actor app shells + route groups, theservices/{domain}+ TanStack Query caching pattern (theauthservice is the template —types.ts/keys.ts/apis/clientApi.ts/hooks/use*.ts/index.ts), the money/format util (formatIrrToToman, Shamsi date display), and shared composites (status chip, stepper/progress header, cards). Reuse the status-chip and the stepper/progress-header composites here — do not build new ones. - f7 booking-request flow (
frontend-phase-7-b8.md): theservices/bookingsdomain seam already exists with the request half (CreateBookingRequest, request list/detail, the C5 awaiting-acceptance status tracker, the nurse request inbox). This phase extends the sameservices/bookingsdomain with the booking/session/EVV half — samekeys.tsfactory, sameclientApi.ts, same hook conventions. Do not create a parallel domain. - The app chrome: the customer mobile shell with the 5-tab bottom nav (the رزروها/Bookings tab is
the customer entry to booking detail), the nurse shell (where EVV and the day's schedule live), the
RSC root layout, themes/tokens,
clientFetch/serverFetch, the cookie manager,AuthContext(roles), the toast bridge. None of this is rebuilt.
Reminder: backend phases own the contracts. If a shape you need (e.g. a session's
payout_eligible_at, the care-instructions decrypt response, the GPS-match result) is missing from bookings-evv.md, you append a request todev/shared-working-context/frontend/requests/for-backend.mdand mock behind theservices/bookingsseam meanwhile (operating-rules §6). You never edit backend files.
2. Required reading (do this first)
../_shared/agent-operating-rules.mdand../_shared/frontend-conventions-checklist.md— how you work and the tick-list (RSC boundary,clientFetch/services-only, Query caching + invalidation, minimal re-renders, MUI primitives reused, i18n both locales, tokens, RTL).../../../client/CLAUDE.mdin full — the engineering contract (layouts, RSC/client boundary, i18n, theme, cookies, fetch services, anti-patterns). Trust the code over any stale doc note (the f0 audit found small drift); fix the doc if you touch that area.- Invoke the
frontend-designerskill — this phase is heavy on visual surfaces (status timeline, a session list with per-session state, the EVV check-in/out screen, the EVV success banner, the care instructions card). All visual work goes through the skill (palette, tokens, typography, theApp*library, RTL rules). Brand: teal#1d4a40, terracotta#d98c6afor financial/EVV affordances — the wireframe gives the nurse view a terracotta border ("نمای پرستار"). ../../../product/business/06-evv-and-service-delivery.md— the EVV business rules: per-session GPS check-in/out, advisory address-match tolerance (evv_location_tolerance_meters), no-show alerting (server-side), payout gated on EVV completion and closed dispute window. Read this before building the EVV screen — the rules are decisions, not guesses.../../../product/wireframes/index.html— the visual baseline. The screens this phase implements:- Booking detail + status timeline — the both-roles view (C5 tracker grows into a full timeline;
states
pending_payment/confirmed/in_progress/completed/disputed/cancelled), session list with per-session schedule/status, money summary (gross/commission/tax labels). - E3 (top half) — the nurse EVV surface: header "ویزیت امروز", the check-in banner
"ورود ثبت شد ۰۹:۰۲ · موقعیت تایید شد (EVV)", today's-task checklist awareness, and the "ثبت خروج
(EVV)" check-out action. (The bottom half of E3 — the free-text visit-note authoring — and the full
E2 patient-record viewer are (DEFERRED) to
frontend-phase-13-b14.md.) - Care-instructions (post-confirm) — the decrypted clinical/logistical context the assigned nurse sees only after confirmation; in the customer flow this is the "Care details" surface authored after pay.
- Booking detail + status timeline — the both-roles view (C5 tracker grows into a full timeline;
states
- bookings-evv.md — the contract you consume. It is the
source of truth for: the booking-detail response (status enum, the three money amounts,
session_count,dispute_window_ends_at), the session shape (booking_session_id, schedule, per-sessionstatus,visit_payout_amount,payout_eligible_at), the EVV check-in/out request+response (check_in_address_matchadvisory result, timestamps, status), and the gated care-instructions read. Do not guess these shapes — derivetypes.tsfrom this doc; file any gap to the for-backend request log and mock meanwhile. ../../contracts/conventions/money-and-types.md+api-conventions.md— the envelope, IRR-as-string + Toman display, enums-as-codes, UTC timestamps + Shamsi display. The session schedule and EVV timestamps are UTC; render them Shamsi. Money renders through the f0formatIrrToTomanutil.- The existing
services/auth/*and the f7services/bookings/*— copy their exact structure; you are extending the latter, not starting fresh.
3. Scope — build this
Everything below lives under the customer mobile shell (booking detail, opened from the رزروها tab) and
the nurse shell (booking detail + the EVV/today surface). All server access goes through the
services/bookings domain (extended from f7). All money via formatIrrToToman; all timestamps Shamsi.
3.1 services/bookings — extend the domain (data layer)
Extend, do not replace, the f7 services/bookings files:
types.ts— add, derived from bookings-evv.md:BookingDetailDto(id, status enumpending_payment|confirmed|in_progress|completed|disputed|closed|cancelled,gross_price_irr/balinyaar_commission_irr/nurse_payout_amountas IRR strings,session_count,dispute_window_ends_at, nurse/patient/address snapshot fields),BookingSessionDto(booking_session_id, scheduled start/end, per-sessionstatusscheduled|in_progress|completed|missed|cancelled,visit_payout_amount,payout_eligible_at),VisitVerificationDto(check-in/out timestamps,check_in_address_matchadvisory boolean/score,status),CareInstructionsDto(decrypted conditions / meds / allergies / instructions / emergency contact — only present in the gated response), and the EVV command DTOsCheckInVisitInput/CheckOutVisitInput(booking_session_id,latitude,longitude, clientcaptured_at). Status enums are codes, mapped to i18n labels in the UI — never display the raw code.keys.ts— add to the existing factory:bookingDetail(id),bookingSessions(bookingId),sessionEvv(sessionId),careInstructions(bookingId). Keep the f7 request keys.apis/clientApi.ts— add the calls (names follow the contract routes; b9 owns the exact paths):getBookingDetail(id),listBookingSessions(bookingId),getCareInstructions(bookingId)(the gated read),checkInVisit(input),checkOutVisit(input),getSessionEvv(sessionId). Each wrapsclientFetch— never rawfetch. Add aserverApi.tsgetBookingDetailonly if you prefetch the detail in an RSC (recommended — see 3.5).- Hooks (one per file) under
hooks/:useBookingDetail(id)—useQueryonbookingDetail(id); deliberatestaleTime(detail changes on status transitions — keep modest, e.g. 30s, and invalidate on EVV mutations so the timeline reflects server truth immediately).useBookingSessions(bookingId)—useQueryonbookingSessions(bookingId).useCareInstructions(bookingId, { enabled })—useQuery,enabledgated bystatus === 'confirmed' || beyondand the viewer being the assigned nurse or admin (see 3.4). When disabled it never fires.useCheckInVisit()/useCheckOutVisit()—useMutation; on success invalidatebookingDetail,bookingSessions, andsessionEvv(so the timeline, the session row, and the EVV banner all reflect the new server state — no manual optimistic money/status math). Surface only domain-specific 4xx messages (e.g. "no open check-in" on check-out); let the fetch layer handle 401/403/5xx toasts.
3.2 Booking detail + status timeline (both roles) — app route + components
- Route: a booking-detail page under the existing
(private-routes)structure, reachable from the customer رزروها/Bookings list (built in f7) and from the nurse booking list. Keep it role-aware viaAuthContext— same page, role-conditioned sections (the EVV actions and care-instructions card render for the assigned nurse; the customer sees the read-only timeline + sessions + money summary). - Status timeline — a shared composite
BookingStatusTimeline(insrc/components/booking/, co-located test) that renders the canonical orderpending_payment → confirmed → in_progress → completed, with terminal branchesdisputed/closed/cancelledshown distinctly. It reflects server truth (BookingDetailDto.status) — never advance a step client-side. Reuse the f0 stepper/progress header primitive underneath; this is the grown-up version of the f7 C5 3-step tracker. - Session schedule list — a
SessionListcomposite renderingBookingSessionDto[], each row aSessionCardshowing Shamsi schedule, a per-session status chip (reuse the f0 status chip — mapscheduled/in_progress/completed/missed/cancelledto labelled, tokenised colours) and, for the assigned nurse, the EVV CTA state (see 3.3). A single-visit booking still renders exactly one session row — do not special-case it away; the data always has ≥1 session. - Money summary — a small
BookingMoneySummaryshowing gross / Balinyaar commission (کارمزد) / and the nurse-payout split, each viaformatIrrToToman. (Tax/مالیات line + the escrow notice are the checkout surface — (DEFERRED) tofrontend-phase-9-b10.md; here just show the confirmed split. If the detail response omits a line, label what you have and don't fabricate.) - States: loading (skeleton), the per-status content (confirmed shows upcoming sessions; in_progress
highlights the active session; completed shows the dispute-window note from
dispute_window_ends_at; cancelled/disputed terminal copy), and an empty/not-found guard if the id isn't the viewer's booking.
3.3 Nurse EVV check-in / check-out (E3 top) — nurse shell
- Today / session-aware surface — within the nurse booking detail (and reachable from the nurse "today"
context), render the per-session EVV control. CTA state machine driven by session + EVV data:
scheduled→ "ثبت ورود (EVV)" (check-in);in_progress(checked-in, no check-out) → "ثبت خروج (EVV)" (check-out);completed→ done (show elapsed duration);missed→ missed state. Today's-task awareness: show the session's task context (the task checklist body is read-only here — authoring is f13). - GPS capture — a small client util
captureGeoPosition()behind a DI-style seam (src/services/bookings/evv/locationProvider.tsexposingILocationProvider) wrapping the browser Geolocation API. On check-in/out: acquire position (loading spinner "در حال دریافت موقعیت…"), then calluseCheckInVisit/useCheckOutVisitwith{ booking_session_id, latitude, longitude, captured_at }. Permission-denied / unavailable is not a hard stop — the product rule says mismatch is advisory, so allow the nurse to proceed (submit without coords or with a flagged value per the contract) and show the advisory banner; never block the visit on GPS. - The EVV check-in banner — on a successful check-in (and whenever an open check-in exists), render the
wireframe banner verbatim in spirit: "ورود ثبت شد {{time}} · موقعیت تایید شد (EVV)" when
check_in_address_matchis true, and the advisory variant "ورود ثبت شد {{time}} · موقعیت خارج از محدوده (در حال بررسی)" when it is false — a tokenised warning banner, not an error, and it does not block check-out. Build it as a shared compositeEvvStatusBanner(co-located test) since both the session card and the day surface use it. Time renders Shamsi/clock from the serverchecked_in_at. - Check-out — requires an open check-in; if none, show the domain message (don't toast the fetch layer's
generic). On success the session row flips to
completed(via the invalidations in 3.1) and shows elapsed duration. Do not compute or display payout-eligibility client-side —payout_eligible_atand the dispute-window gate are server truth; render whatever the detail/session response gives you.
3.4 Care-instructions read (two-stage disclosure) — gated card
- A shared
CareInstructionsCard(insrc/components/booking/, co-located test) that renders the decryptedCareInstructionsDto(conditions / medications / allergies / instructions / emergency contact). - Gate the UI, hard: the card and its query (
useCareInstructions) render/fire only when the booking isconfirmedor beyond and the current user is the assigned nurse (or admin). For anyone else — the customer, an unassigned nurse, a pre-confirmation viewer — the card is not rendered and the query never fires. This is the client mirror of the server's gatedGetCareInstructions; the server is the real boundary, but the UI must not even request instructions it has no right to (a 403 from the server is a defect path, not the design). Show a neutral "visible to your assigned nurse and support only" affordance to the customer instead. - The customer-side authoring of care details (the post-confirmation "Care details" form,
SubmitCareInstructions) is the write path — if b9's contract exposes it, build a minimal read-back here; the full encrypted authoring form is owned by the booking write flow and may be (DEFERRED) to f13 if the contract doesn't surface it in b9. Read the contract; if absent, mock the read and log the gap.
3.5 RSC prefetch (remove a client round-trip)
Where the booking detail is the first paint, prefetch getBookingDetail(id) in the RSC and hand it to the
client tree via initialData / a hydrated query (the f0 pattern). Keep the EVV mutations + care-instructions
on the client. Respect the RSC/client boundary (no @/lib/cookies/client in an RSC; no
next-intl/server/next/headers in a client component).
3.6 i18n + tokens
- Add the user-visible strings under the existing
bookingnamespace (and reusecommon/nav) in bothmessages/en.jsonandmessages/fa.json, in sync — status labels, session statuses, the EVV banner strings (in-range + advisory out-of-range), check-in/out CTAs, GPS-acquiring text, the care-instructions section labels + the customer "visible to your nurse only" copy, the dispute-window note.fais default + RTL — design RTL-first and verify mirroring (the timeline and session rows must read right-to-left correctly). - All colours from
tokens.css(var(--bal-…)); the EVV/financial terracotta affordance via the brand token, the advisory banner via the warning token, status chips via the status-chip tokens. No hardcoded colours insx. MUI v9 API only; reuseAPP_THEME_LTR/RTL.
Out of scope (DEFERRED — pointers, do not build here):
- Full patient-record viewer (E2) and nurse visit-NOTE authoring (E3 bottom) →
frontend-phase-13-b14.md. - Checkout / pay / escrow notice / invoice / tax line →
frontend-phase-9-b10.md. - Cancellation flow + refund/policy-fee disclosure →
frontend-phase-10-b11.md. - Admin EVV-review queue (location-mismatch / no-show worklist) → admin console
frontend-phase-15-b15.md. This phase raises no alerts client-side; no-show detection is a server job.
4. Mocks & seams in this phase
- This phase introduces one client seam:
ILocationProvider(src/services/bookings/evv/locationProvider.ts) — wraps the browser Geolocation API for EVV GPS capture. The real implementation callsnavigator.geolocation.getCurrentPosition; a mock returns canned coordinates so the EVV flow is testable without a device and so a denied/unavailable path can be exercised. Record it indev/shared-working-context/reports/mocks-registry.mdwith the seam, what it fakes, the config key (e.g.NEXT_PUBLIC_EVV_MOCK_GPS), and how to make it real. Server-side GPS/address-match math lives behind the backend's geocoding/geo-distance seam — reuse it from b9, do not introduce a server seam here. services/bookingsdata, if b9 isn't merged yet: build the booking/session/EVV/care calls behind the sameservices/bookingsclientApias a mockclientApi(real-shaped per the contract), select it by config exactly as f0 established, and record it in your frontend report so it swaps cleanly when the real endpoints land. The EVV mutations' mock should flip the mocked session/EVV state so the banner + timeline transitions are demonstrable.- Everything else is reused:
clientFetch/serverFetch, the Query client, the cookie/auth manager, the toast bridge, the f0 composites (status chip, stepper). Introduce no new cross-cutting seam beyondILocationProvider.
5. Critical rules you must not get wrong
- Two-stage clinical disclosure is a UI gate, not just a server check. Care instructions render and are
queried only when the booking is
confirmed+ and the viewer is the assigned nurse or admin. An unassigned user — including the customer and any other nurse — must never see the instructions and the client must never even fire the request. Treat a 403 as a defect path, not the intended flow. - A GPS / address mismatch is advisory — a banner, never a block. Out-of-range check-in still succeeds; show the warning-tokened "موقعیت خارج از محدوده (در حال بررسی)" banner and let check-out proceed. Never auto-cancel, never withhold the visit, never gate check-out on the match. Permission-denied/unavailable GPS likewise must not block the nurse.
- The status timeline reflects server truth.
BookingDetailDto.statusis the single source — never advance, infer, or optimistically jump a timeline step on the client. After an EVV mutation, invalidate the detail/session queries and re-render from the server response. - Money is display-only here and never computed. Render
gross_price_irr/balinyaar_commission_irr/nurse_payout_amountexactly as the server sends them (IRR strings, integer-safe), throughformatIrrToToman. Do not sum, derive, or re-split amounts on the client; the booking money identity (gross = commission + payout) and per-sessionvisit_payout_amountare computed and guaranteed server-side. Do not compute payout-eligibility —payout_eligible_atis gated by the dispute window server-side; one payout per booking is enforced server-side; render, don't recompute. - A single-visit booking still has exactly one session. Render the one
booking_sessionsrow through the sameSessionCardpath; no "0 sessions" or single-visit special case. - Caching is a feature. Set deliberate
queryKey/staleTime; invalidate on every EVV mutation so the timeline, session row, and EVV banner update without a full refetch storm. Don't refetch data already in cache; prefer the RSC prefetch for first paint. - Boundaries & primitives: respect the RSC/client boundary; fetch only through
services/bookings→clientFetch/serverFetch; MUI primitives stay MUI; the shared composites (BookingStatusTimeline/SessionCard/EvvStatusBanner/CareInstructionsCard) live at the shared level with co-located tests, not buried in a page. Minimise re-renders (stable refs,select, colocated state). - i18n + RTL + tokens: every string in both locale files, in sync;
fadefault & RTL-correct; colours fromtokens.css; MUI v9 only. Invoke the frontend-designer skill for all visual work.
6. Definition of Done
The shared definition-of-done.md, plus:
- Booking detail renders for both roles from
useBookingDetail, with a server-truthBookingStatusTimelinecovering all seven statuses and aSessionList(≥1 session, single-visit included), money summary viaformatIrrToToman. - Nurse EVV: check-in captures GPS through
ILocationProvider, posts viauseCheckInVisit, and shows the in-range "ورود ثبت شد … موقعیت تایید شد (EVV)" banner or the advisory out-of-range variant; check-out viauseCheckOutVisitrequires an open check-in and flips the session tocompleted(server-driven, via invalidation). GPS denial does not block. - Care-instructions card + query are gated: visible/fired only for the assigned nurse (or admin) on a
confirmed+ booking; never rendered/requested for the customer or an unassigned user (verified by test). - All new strings in
en.jsonandfa.json, in sync; RTL verified; colours from tokens; MUI v9. services/bookingsextended (not duplicated); hooks invalidate on EVV mutation; types derived from bookings-evv.md (gaps logged to for-backend + mocked).- The shared composites (
BookingStatusTimeline,SessionCard,EvvStatusBanner,CareInstructionsCard) each have a co-located*.test.tsx;npm run checkgreen;npm run test:cigreen. client/CLAUDE.mdProject Structure updated for the newsrc/components/booking/folder, theservices/bookingsextension, and theILocationProviderevv seam.
7. How to test (what a human can verify after this phase)
Run npm run dev (with the b9 endpoints live, or the mock clientApi selected by config). Walk these:
- Open a confirmed booking from the customer رزروها/Bookings tab → the page shows the status timeline
sitting at
confirmed, the session schedule (single-visit shows exactly one session; multi-day shows N), and the money summary in Toman. Switch locale → strings +dirflip correctly, timeline reads RTL. - As the assigned nurse, open the same booking → the care-instructions card is visible (conditions/ meds/allergies/instructions/emergency contact). As the customer or an unassigned nurse, the card is absent and the network tab shows the care-instructions request was never made.
- Nurse check-in on today's session → "در حال دریافت موقعیت…" spinner, then the EVV banner "ورود ثبت
شد ۰۹:۰۲ · موقعیت تایید شد (EVV)"; session chip →
in_progress; timeline →in_progress. With the mock GPS forced out-of-range, the banner shows the advisory out-of-range variant and check-in still succeeds. Deny GPS permission → the nurse can still proceed. - Nurse check-out → session chip →
completedwith elapsed duration; the booking timeline advances tocompletedfrom the server response (no client-side step jump). Check-out before any check-in → the domain "no open check-in" message, no generic toast. - Caching: in React Query Devtools, the EVV mutation invalidates
bookingDetail/bookingSessions/sessionEvvand the UI re-renders from the refetch; revisiting the page withinstaleTimedoes not refetch. npm run checkandnpm run test:cipass; the gating test proves the customer/unassigned path never renders or requests care instructions.
8. Hand off & document (close the phase)
- Docs: update the Project Structure tree in
client/CLAUDE.mdforsrc/components/booking/, the extendedservices/bookings, and theservices/bookings/evvlocation seam; add a short note that the booking-detail/EVV pattern (timeline + sessions + gated care + EVV banner) is the template f9/f13 extend. Fix any doc drift you touch. If you discover/confirm a business rule theproduct/docs don't capture (e.g. the exact advisory-banner copy or the post-confirm disclosure timing), record it inproduct/business/06-evv-and-service-delivery.md(don't invent rules) and regenerate the HTML perproduct/CLAUDE.md. - Contract (consume): types/services derive from
dev/contracts/domains/bookings-evv.md(b9). Any missing or ambiguous shape — care-instructions decrypt response,check_in_address_matchrepresentation,payout_eligible_atexposure, the care-details write-back — is appended todev/shared-working-context/frontend/requests/for-backend.md(you never edit backend files), and mocked behindservices/bookingsmeanwhile. - Handoff & report: append your phase summary to
dev/shared-working-context/frontend/STATUS.md; writedev/shared-working-context/reports/frontend-phase-8-report.md— what was built (booking detail/timeline, sessions, nurse EVV, gated care), what is now testable and exactly how (the §7 steps), what is mocked (ILocationProvider, any mockedservices/bookingscalls) and how to make it real, the contract consumed- gaps filed, follow-ups for f9/f13. Update
dev/shared-working-context/reports/mocks-registry.mdfor theILocationProviderseam and any mocked endpoint.
- gaps filed, follow-ups for f9/f13. Update
- Memory: save a
projectmemory note for the non-obvious decisions — the two-stage care-instructions UI gate (don't-even-fetch), the advisory-not-blocking EVV mismatch handling, theILocationProviderGPS seam, and that the status timeline is strictly server-truth — with a one-line pointer added toMEMORY.md.