Files
2026-06-28 21:59:59 +03:30

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, the services/{domain} + TanStack Query caching pattern (the auth service 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): the services/bookings domain 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 same services/bookings domain with the booking/session/EVV half — same keys.ts factory, same clientApi.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 to dev/shared-working-context/frontend/requests/for-backend.md and mock behind the services/bookings seam meanwhile (operating-rules §6). You never edit backend files.

2. Required reading (do this first)

  • ../_shared/agent-operating-rules.md and ../_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.md in 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-designer skill — 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, the App* library, RTL rules). Brand: teal #1d4a40, terracotta #d98c6a for 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.
  • bookings-evv.mdthe 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-session status, visit_payout_amount, payout_eligible_at), the EVV check-in/out request+response (check_in_address_match advisory result, timestamps, status), and the gated care-instructions read. Do not guess these shapes — derive types.ts from 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 f0 formatIrrToToman util.
  • The existing services/auth/* and the f7 services/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 enum pending_payment|confirmed|in_progress|completed|disputed|closed|cancelled, gross_price_irr/balinyaar_commission_irr/nurse_payout_amount as IRR strings, session_count, dispute_window_ends_at, nurse/patient/address snapshot fields), BookingSessionDto (booking_session_id, scheduled start/end, per-session status scheduled|in_progress|completed|missed|cancelled, visit_payout_amount, payout_eligible_at), VisitVerificationDto (check-in/out timestamps, check_in_address_match advisory boolean/score, status), CareInstructionsDto (decrypted conditions / meds / allergies / instructions / emergency contact — only present in the gated response), and the EVV command DTOs CheckInVisitInput/CheckOutVisitInput (booking_session_id, latitude, longitude, client captured_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 wraps clientFetchnever raw fetch. Add a serverApi.ts getBookingDetail only if you prefetch the detail in an RSC (recommended — see 3.5).
  • Hooks (one per file) under hooks/:
    • useBookingDetail(id)useQuery on bookingDetail(id); deliberate staleTime (detail changes on status transitions — keep modest, e.g. 30s, and invalidate on EVV mutations so the timeline reflects server truth immediately).
    • useBookingSessions(bookingId)useQuery on bookingSessions(bookingId).
    • useCareInstructions(bookingId, { enabled })useQuery, enabled gated by status === 'confirmed' || beyond and the viewer being the assigned nurse or admin (see 3.4). When disabled it never fires.
    • useCheckInVisit() / useCheckOutVisit()useMutation; on success invalidate bookingDetail, bookingSessions, and sessionEvv (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 via AuthContext — 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 (in src/components/booking/, co-located test) that renders the canonical order pending_payment → confirmed → in_progress → completed, with terminal branches disputed/closed/cancelled shown 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 SessionList composite rendering BookingSessionDto[], each row a SessionCard showing Shamsi schedule, a per-session status chip (reuse the f0 status chip — map scheduled/in_progress/completed/missed/cancelled to 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 BookingMoneySummary showing gross / Balinyaar commission (کارمزد) / and the nurse-payout split, each via formatIrrToToman. (Tax/مالیات line + the escrow notice are the checkout surface — (DEFERRED) to frontend-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.ts exposing ILocationProvider) wrapping the browser Geolocation API. On check-in/out: acquire position (loading spinner "در حال دریافت موقعیت…"), then call useCheckInVisit/useCheckOutVisit with { 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_match is 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 composite EvvStatusBanner (co-located test) since both the session card and the day surface use it. Time renders Shamsi/clock from the server checked_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-sidepayout_eligible_at and 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 (in src/components/booking/, co-located test) that renders the decrypted CareInstructionsDto (conditions / medications / allergies / instructions / emergency contact).
  • Gate the UI, hard: the card and its query (useCareInstructions) render/fire only when the booking is confirmed or 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 gated GetCareInstructions; 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 booking namespace (and reuse common/nav) in both messages/en.json and messages/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. fa is 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 in sx. MUI v9 API only; reuse APP_THEME_LTR/RTL.

Out of scope (DEFERRED — pointers, do not build here):

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 calls navigator.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 in dev/shared-working-context/reports/mocks-registry.md with 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/bookings data, if b9 isn't merged yet: build the booking/session/EVV/care calls behind the same services/bookings clientApi as a mock clientApi (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 beyond ILocationProvider.

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.status is 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_amount exactly as the server sends them (IRR strings, integer-safe), through formatIrrToToman. Do not sum, derive, or re-split amounts on the client; the booking money identity (gross = commission + payout) and per-session visit_payout_amount are computed and guaranteed server-side. Do not compute payout-eligibilitypayout_eligible_at is 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_sessions row through the same SessionCard path; 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/bookingsclientFetch/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; fa default & RTL-correct; colours from tokens.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-truth BookingStatusTimeline covering all seven statuses and a SessionList (≥1 session, single-visit included), money summary via formatIrrToToman.
  • Nurse EVV: check-in captures GPS through ILocationProvider, posts via useCheckInVisit, and shows the in-range "ورود ثبت شد … موقعیت تایید شد (EVV)" banner or the advisory out-of-range variant; check-out via useCheckOutVisit requires an open check-in and flips the session to completed (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.json and fa.json, in sync; RTL verified; colours from tokens; MUI v9.
  • services/bookings extended (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 check green; npm run test:ci green.
  • client/CLAUDE.md Project Structure updated for the new src/components/booking/ folder, the services/bookings extension, and the ILocationProvider evv 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:

  1. 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 + dir flip correctly, timeline reads RTL.
  2. 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.
  3. 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.
  4. Nurse check-out → session chip → completed with elapsed duration; the booking timeline advances to completed from the server response (no client-side step jump). Check-out before any check-in → the domain "no open check-in" message, no generic toast.
  5. Caching: in React Query Devtools, the EVV mutation invalidates bookingDetail/bookingSessions/ sessionEvv and the UI re-renders from the refetch; revisiting the page within staleTime does not refetch.
  6. npm run check and npm run test:ci pass; 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.md for src/components/booking/, the extended services/bookings, and the services/bookings/evv location 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 the product/ docs don't capture (e.g. the exact advisory-banner copy or the post-confirm disclosure timing), record it in product/business/06-evv-and-service-delivery.md (don't invent rules) and regenerate the HTML per product/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_match representation, payout_eligible_at exposure, the care-details write-back — is appended to dev/shared-working-context/frontend/requests/for-backend.md (you never edit backend files), and mocked behind services/bookings meanwhile.
  • Handoff & report: append your phase summary to dev/shared-working-context/frontend/STATUS.md; write dev/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 mocked services/bookings calls) and how to make it real, the contract consumed
    • gaps filed, follow-ups for f9/f13. Update dev/shared-working-context/reports/mocks-registry.md for the ILocationProvider seam and any mocked endpoint.
  • Memory: save a project memory note for the non-obvious decisions — the two-stage care-instructions UI gate (don't-even-fetch), the advisory-not-blocking EVV mismatch handling, the ILocationProvider GPS seam, and that the status timeline is strictly server-truth — with a one-line pointer added to MEMORY.md.