Files
baya-monorepo/dev/shared-working-context/reports/ui-phase-7-report.md
T
2026-07-19 11:56:59 +03:30

15 KiB
Raw Blame History

UI Phase 7 — Nurse Daily Ops — Report (2026-07-19)

What was built

The nurse "امروز" dashboard (nurse/page.tsxNurseDashboardScreen.tsx, new) — replaces the PlaceholderScreen with pure assembly over already-cached queries:

  • Greeting header (name off useMe) + own TrustBadge (ownBadgeState, one cached useVerificationStatus query, not two).
  • NextVisitCard — first actionable session from useTodaySessions, a display-only "starts in" line (formatRelativeTime), a check-in shortcut to /nurse/visits; a calm «امروز ویزیتی ندارید» empty state.
  • RequestsStrip — the most time-critical widget (sorts above earnings): pending count, the most urgent request's urgency-tinted countdown, an inline open into the request detail.
  • EarningsSnapshotCard — a compact two-stat row (signed net payable + eligible) via useNurseEarningsBalance
    • <Money>; never clamps a negative balance.
  • DashboardActivationSlot (new, exported, page-local) — the named composition point for Phase 8. Filled for now with only the existing verification-status banner when not yet approved; renders nothing once approved. Phase 8: extend client/src/app/[locale]/(private-routes)/nurse/DashboardActivationSlot.tsx in place — don't add a second slot.
  • NotificationsEntryRow — unread count (useUnreadCount) linking to ROUTES.NURSE_NOTIFICATIONS.
  • Every widget: skeleton → error-with-retry → empty → data, in that order.

Visits day surface (visits/page.tsx):

  • Shamsi «امروز، {day month}» date anchor via PageHeader (replaces the static title).
  • useTodaySessions gained a 60s refetchInterval (TODAY_SESSIONS_REFETCH_MS) so a same-day schedule change surfaces without re-navigation; the EVV-mutation invalidation is untouched.
  • SessionCard gained an optional serviceLabel prop, rendered under the title; the day surface passes item.variantLabel (REQ-052, mock-tolerant — undefined on the real path just omits the line).
  • A real error state (ErrorState + retry) replaces the old two-field { data, isLoading } destructure that rendered "no visits today" on a failed query.

SessionCard EVV hero (shared, both roles — gated on showEvvControls):

  • Check-in/check-out are now the full-width, ≥48px primary action (width:'100%', minHeight:48), replacing the small alignSelf:'flex-start' button visually equal to the "view booking" text link beside it.
  • Check-out now goes through the new shared CheckOutConfirmButton (components/booking/, tested) — a lightweight ConfirmDialog before firing («اتمام ویزیت؟» — it ends the visit and starts the payout clock). Reused by BookingDetailView's in-visit banner so both entry points confirm the same way.

Visit detail workspace (BookingDetailView.tsx, shared both roles):

  • Address card (new, standalone — replaces the old inline hero address line): renders the frozen addressSnapshotJson when present (title/city/district/line, best-effort parse — REQ-045 still open), a geo:{lat},{lng} map deep-link + a Neshan web-map fallback link when the snapshot carries coordinates (REQ-051), and a quiet nurse-only "available after confirmation" note when masked. Never sourced from the b8 request stage.
  • In-visit mode (new, nurse-only): when a session is checked_in, an AccentCard state header — «در حال ویزیت» + elapsed on-site time (formatElapsed against the server checkInAt, never a guessed start) + the check-out CTA promoted to the top, via the same CheckOutConfirmButton.
  • Contact affordance — already delivered, verified, no changes needed. BookingSupportEntry/ EmergencyBanner (messaging composites, already mounted on visits/[id]/page.tsx) already surface a tel: click-to-call from the gated care-instructions read for the nurse on a confirmed+ booking. The phone/navigate icons the phase doc flagged as missing are also already registered (AppIcon/config.ts) — both audit findings were stale against current code.
  • Notes placement: NurseVisitNotesPanel/BookingSupportEntry/BookingDetailView were already each maxWidth:640, mx:'auto', width:'100%' and stacked under one gap:3 — verified consistent, no change needed.

Request inbox redesign (requests/page.tsx, requests/[id]/page.tsx):

  • Decision-first InboxCard: service + price headline when the list item carries variantLabel/ variantPrice (REQ-050, mock-tolerant — degrades to the patient-name headline on the real path), patient/ time/gender as secondary facts. The whole card is now a tappable link (AppLink), matching the "fully tappable AccentCard row" convention from the customer bookings screen.
  • Urgency-tinted countdown pill: CountdownTimer's existing v2 tier API (warnThresholdSeconds/ urgentThresholdSeconds) at teal >2h / amber <2h / terracotta <30min, a label, and coarseLabel (which opts into aria-live="polite" humanized copy) — no extension to CountdownTimer was needed, its ui-phase-1 API already covers this.
  • Tabs + pager: «در انتظار» (pending_nurse_response) / «پاسخ‌داده» (client-merged accepted_awaiting_payment
    • converted + rejected_by_nurse, page-1-only — documented limitation, filed as part of REQ-050's status-group-filter ask) / «منقضی» (expired_no_response). useNurseRequestInbox gained an optional enabled param so the four non-active tab queries don't poll in the background.
  • Accept now requires a ConfirmDialog («با پذیرش، خانواده برای پرداخت دعوت می‌شود؛ پس از پرداخت، رزرو قطعی می‌شود.») before firing — the payment-window duration is never hard-coded; after acceptance the detail page renders a CountdownTimer against the server paymentDeadlineAt.
  • The inbox's error handling (isErrorErrorState) was already correct in the current code — the phase doc's cited line numbers were stale (a prior phase had already fixed the false-empty defect).

Earnings clarity (earnings/page.tsx, PayoutHistoryRow, payout detail page):

  • «برداشت بعدی» ForecastLine above the tabs — server-served only (nextPayoutDate/ nextPayoutEligibleAmountIrr, both optional on NurseEarningsSummary), renders nothing until served (REQ-053, never computed client-side).
  • Failure-reason mapping (services/payouts/failureReasons.ts, new): known failureReason codes (today: invalid_sheba) render a mapped Persian/English label as the headline; unknown codes get a generic message; the raw code is always demoted to a secondary dir="ltr" caption — never the raw vendor string as the headline. Applied to both PayoutHistoryRow and the payout detail page.
  • ExplainerCard a11y: the bare onClick Stack is now a real ButtonBase (aria-expanded, aria-controls) and the eye icons (visibilityon/visibilityoff) are replaced by the registered expand chevron (rotates 180° when open).
  • Width normalization: adopted one page-level convention — CONTENT_MAX_WIDTH (800) + mx:'auto' — across every page this phase touches (requests/page.tsx, requests/[id]/page.tsx, visits/page.tsx, earnings/page.tsx, earnings/payouts/page.tsx, earnings/payouts/[id]/page.tsx). The dashboard uses a wider DASHBOARD_MAX_WIDTH (960) per the phase's "the dashboard may go wider" allowance. BookingDetailView (640) is a component-level width, untouched — a separate decision from the page-level convention.

New shared component: Pager (components/common/Pager/, tested) — the prev/next "page X of Y" control, replacing three near-identical inline pagers (earnings, payout history, and the new inbox tabs). New common namespace i18n keys (page_prev/page_next/page_indicator) so it doesn't depend on the payouts namespace's copies (left in place, unused by the new code, but not deleted — out of scope to chase down every call site).

REQ-039…043 renumbered to REQ-050…054 — the phase doc was written assuming REQ-001…038 were taken, but ui-phase-3 through ui-phase-6 have since filed REQ-039…049. Filed as REQ-050 (inbox decision data + status-group filter), REQ-051 (nurse-view address post-confirmation), REQ-052 (today-feed service label), REQ-053 (payout forecast), REQ-054 (web-push, deferred/non-blocking) — see for-backend.md.

What is now testable (and exactly how)

  1. Log in as the seeded verified nurse (refinement-phase-1 demo accounts) → /nurse shows a real dashboard: greeting + trust badge, next visit (or the calm empty state), a pending-requests strip, an earnings snapshot, and (if not yet approved) the activation banner.
  2. /nurse/visits (mobile, /fa): a Shamsi «امروز، …» header; check-in is a full-width primary CTA; check-out opens a confirm dialog first. Deny browser GPS → check-in still succeeds with the advisory warning toast.
  3. /nurse/requests: three tabs; a pending card leads with service + price only when USE_BOOKING_REQUESTS_MOCK=true (flip the flag in services/bookingRequests/constants.ts — the real path degrades to the patient-name headline); the countdown pill is teal/amber/terracotta by remaining time (seed/adjust a request's deadline to see the escalation). Open a request → «پذیرش» → confirm dialog → accept → the detail shows a payment-window countdown.
  4. Stop the API (or force a query error) on /nurse/requests or /nurse/visits → an ErrorState panel with retry, never the empty state.
  5. /nurse/visits/[id] with USE_BOOKINGS_MOCK=true on a confirmed booking (e.g. id 5001 or the completed 5005) → an address card with a working geo:/Neshan-web map link (booking 5001's seed now carries lat/lng matching the EVV reference point); on the real (masked) path → the quiet "available after confirmation" note, no crash. Check in on a today session, then open its booking detail → the «در حال ویزیت» banner with elapsed time + a promoted check-out CTA.
  6. /nurse/earnings: the forecast line always appears (payouts stays mock-primary, USE_PAYOUTS_MOCK=true by default); a failed payout (seeded invalid_sheba) shows a mapped Persian/ English label as the headline with the raw code as a small LTR caption; the explainer header opens with Enter/Space and reports aria-expanded.
  7. Repeat 16 on /en and dark mode — no stock-MUI colors, no Latin digits in fa timers.

Verification performed: npm run check (tsc + eslint) is green; npm run test:ci is green — 106 suites / 455 tests, including the 2 new suites (Pager, CheckOutConfirmButton) and every touched shared component's existing suite (SessionCard, BookingDetailView, PayoutHistoryRow). A dev-server smoke pass confirmed every touched route (/nurse, /nurse/requests[/1], /nurse/visits[/5001], /nurse/earnings[/payouts[/9001]], /en/nurse) compiles and responds 200 with no server errors. Not performed: a full authenticated click-through in a browser (no browser-automation tool available in this session, and exercising the OTP login flow end-to-end needs the backend API running) — the "How to test" steps above are written for a human to run that pass manually.

What is mocked / waiting on a real service

  • services/bookingRequestsreal by default (USE_BOOKING_REQUESTS_MOCK=false); the mock's toListItem now also stamps variantLabel/variantPrice/variantPriceUnit (REQ-050) so the redesigned inbox card is demonstrable when the flag is flipped for local testing. No registry row (bookingRequests was already de-mocked in refinement-phase-4 and has none — consistent with its siblings).
  • services/bookingsreal by default (USE_BOOKINGS_MOCK=false); mock changes: forViewer now unmasks addressSnapshotJson for the nurse once isBookingConfirmedOrBeyond (simulating REQ-051 ahead of the real endpoint), addr5001 gained matching latitude/longitude, and listTodaySessions stamps variantLabel (REQ-052). See the updated mocks-registry.md row.
  • services/payoutsmock-primary (USE_PAYOUTS_MOCK=true, unchanged — REQ-025 is still the root gap); buildSummary() now also serves nextPayoutDate/nextPayoutEligibleAmountIrr (REQ-053). See the updated mocks-registry.md row.
  • No new seams introduced this phase — every change extends an existing services/{domain} mock behind its existing seam.

Contracts

  • Consumed: dev/contracts/domains/{booking-requests,bookings-evv,payouts}.md (unchanged this phase — no contract landed to consume).
  • Filed to for-backend.md: REQ-050 (nurse inbox variantLabel/variantPrice/variantPriceUnit + an answered status-group filter), REQ-051 (nurse-view address on confirmed+ bookings), REQ-052 (service label on the today feed), REQ-053 (payout forecast), REQ-054 (web-push, deferred).

Docs updated

  • client/CLAUDE.md "Project Structure": the nurse route tree (page.tsxNurseDashboardScreen.tsx + DashboardActivationSlot.tsx, requests/, visits/, earnings/) and the shared component tree (components/common/Pager/, components/booking/'s CheckOutConfirmButton + the BookingDetailView/ SessionCard additions) updated in the same change.
  • dev/shared-working-context/reports/mocks-registry.md: BookingsApi and PayoutsApi rows updated in place with this phase's mock additions (also corrected BookingsApi's stale "default true" config-flag note — it's false, real-primary, since refinement-phase-4).

Follow-ups for later phases

  • Phase 8 owns DashboardActivationSlot's content — the fuller "go live" checklist (profile/services/ coverage/bank all done), per the phase doc's own hand-off note.
  • REQ-050's status-group filter (status=answered) would collapse the inbox's three-query «پاسخ‌داده» merge into one real paginated query — currently page-1-only, documented in the REQ and in code.
  • REQ-051 (nurse-view address) is the highest-value of this phase's REQs — until delivered, the address card is demoable only via the mock; REQ-045 (a typed address-snapshot shape) is still open from ui-phase-5 and would let the address card + .ics export drop their best-effort JSON-key parsing.
  • The payouts namespace's now-unused failure_reason_label/page_prev/page_next/page_indicator i18n keys were left in place (superseded by failure_code_* and the shared common.page_* keys respectively) — a future cleanup pass could remove them if nothing else references them.
  • Web-push (REQ-054) remains genuinely deferred — the 15s poll is the only freshness mechanism for new requests until a service-worker + backend push rail is built.