Files
baya-monorepo/dev/shared-working-context/reports/ui-phase-5-report.md
T
2026-07-18 09:51:03 +03:30

15 KiB
Raw Blame History

UI Phase 5 — Booking Lifecycle — Report (2026-07-18)

What was built

C4 request formbookings/request/page.tsx:

  • A sticky nurse-identity bar (NurseIdentityBar, page-local) pinned to the top of the scroll: avatar, name, rating + review count, tappable TrustBadge, gender chip — off the already-fetched useNurseProfile. The family always sees who they're inviting home before filling anything in.
  • A «چه اتفاقی می‌افتد؟» strip reusing C5's own StepperHeader three-step labels (step_submitted / step_awaiting / step_payment) at activeStep={0} — no new one-off stepper, and the copy stays in sync with the tracker the family lands on next.
  • The native type="date" field replaced by the new shared JalaliDateIntentPicker (extracted from C1's local date-intent widget — see below) and the free start/end time fields replaced by tappable morning/afternoon/evening window chips + a «زمان دلخواه» custom option that reveals the time fields — kills the end≤start error class for the common case.
  • Fixed the dead-validation defect: attempted (a single flag only ever set inside the submit handler the disabled button could never reach) replaced with a per-field touched map set onBlur; inline errors are now reachable. The submit button stays disabled while required fields are missing (unchanged behavior) but a caption underneath now lists exactly what's missing («برای ادامه: انتخاب بیمار، تاریخ»), built from cta_missing_* keys.
  • The fake-map preview (AddressMapPicker wrapped in pointerEvents:'none') replaced with a compact address row (icon + "title · city · district" + street line) and a «تغییر» affordance that swaps the row back for the select; choosing a new address collapses it back to the compact row automatically.
  • Negative-margin stitching (mt: -1.5, mt: -2) removed — price-under-select and counter-under-notes are now grouped with real Stack containers.
  • C4 now also reads patient_id/address_id query params (extending the existing nurse_id/variant_id/ required_gender handoff from C3) so C5's terminal-state "request again" can reopen it fully prefilled.

New shared componentcomponents/common/JalaliDateIntentPicker/: extracted from C1's local DateIntentFilter (near-day chip strip + a calendar-icon Popover entry into the full Jalali grid) so C4's real, required date field could reuse the identical widget instead of forking a second copy. C1's SearchScreen.tsx was refactored to delegate to it (behavior byte-identical — verified via npm run test:ci); the new component ships its own co-located test.

C5 trackerbookings/request/[id]/page.tsx:

  • The response countdown now renders CountdownTimer's progress ring (windowStart={request.createdAt}, the exact server-frozen pair — no client-side deadline math) with a humanized coarse label above the 10-minute threshold («حدود ۳ ساعت» / «حدود ۲۵ دقیقه», new countdown_about_hours/countdown_about_minutes keys) that switches to the ticking clock in the final minutes. A one-line «نتیجه را به شما اطلاع می‌دهیم» note sits under it. The payment countdown is unchanged (no accurate windowStart exists on the DTO for it — never fabricated client-side).
  • Fixed the cancel-dialog label defect: the hand-rolled Dialog replaced with the shared ConfirmDialog. Dismiss now reads «نه، نگه دار» (cancel_confirm_keep, neutral text button); the destructive action reads «بله، انصراف از درخواست» (cancel_confirm_destructive, error/contained) — the dismiss button no longer carries the destructive action's own label. The now-unused cancel_confirm_yes key was removed from both message files.
  • Terminal-state recovery for rejected_by_nurse/expired_no_response: a «درخواست دوباره با زمان دیگر» button reopens C4 prefilled with the same nurse/variant/patient/address, and a «پرستاران مشابه» button opens /search carrying the request's city/district/gender. For rejections, the same-nurse retry is suppressed when nurseRejectionReason (freeform text — no structured code exists, REQ-044) matches a gender/coverage keyword heuristic (fa+en), in which case only "similar nurses" is offered. Other terminal states (payment_deadline_expired, cancelled_by_customer, converted) are unchanged.

Bookings listbookings/page.tsx + new BookingsScreen.tsx:

  • Three segmented tabs: «در انتظار پاسخ» / «فعال» / «گذشته». The pending tab wires the previously-exported-but-unused useCustomerRequests (filtered to pending_nurse_response + accepted_awaiting_payment), with a live mini CountdownTimer per row deep-linking to C5 — the "orphaned request" defect (money-adjacent deadline the customer could no longer find) is fixed. A badge on the tab label shows the pending count.
  • Active/past split useBookingList('customer') client-side by status over one query with a growing pageSize ("load more", the exact C2 results pattern) — booking #21+ is reachable via the button.
  • Rows are AccentCards with a status-toned borderInlineStart + a soft StatusChip, role="button" + keyboard-activatable, fully tappable (not just a small nested button).
  • A completed/closed row without a review shows a compact star-strip CTA (RatingInput read-only decor + «ثبت نظر» text) via a gated useReviewEligibility(bookingId, { enabled: isCompleted }) — no extra query fires for non-completed rows.

Booking detailBookingDetailView.tsx:

  • The header is now a hero: a next-upcoming-session headline («ویزیت ۲ · فردا ۰۹:۰۰», via formatRelativeTime + a Shamsi fallback past 7 days, bidi-isolated clock digits), the frozen visit address (best-effort parsed off addressSnapshotJson — see REQ-045), a nurse-identity row, and a client-side .ics add-to-calendar download (new components/booking/ics.ts, no backend seam — Gregorian UTC in the file, Shamsi in the UI).
  • An EVV presence headline («پرستار در محل است · ورود ۰۹:۰۲», success-toned) renders above the address whenever a session is currently checked in — elevates the existing advisory EVV data instead of leaving it buried per-session. (The equivalent compact list-row indicator from the phase brief was not built: BookingListItemDto carries no per-row EVV/session state, and fetching it via an extra query per in-progress row would be an N+1 anti-pattern — filed as a note, not a REQ, since no clean single-field addition was obvious; flagged for a human product/API call.)
  • The vertical StatusTimeline swap the phase asked for (in place of StepperHeader) was already done in ui-phase-1 (BookingStatusTimeline already renders StatusTimeline) — verified, not re-built; the stale in-code comment claiming otherwise is fixed in this pass.
  • Fixed the unnamed_nurse key-misuse defect: a new bd_nurse_label ("پرستار") key is now the nurse header-fact label; unnamed_nurse stays exactly the no-name fallback value it was written for.
  • SessionCard's outer Paper swapped for SurfaceCard (identical padding="sm" visual, now on the shared radius token) — the "align to phase-1 card anatomy" ask.

Cancel flowbookings/[id]/cancel/page.tsx:

  • Two off-ramps above the disclosure: «تغییر زمان» and «گفتگو با پشتیبانی», both opening the existing ContactSupportDialog (pre-linked to the booking, categories coordination/support respectively) plus a one-line nurse-impact note. Real rescheduling stays DEFERRED (product decision + backend, per the phase brief — no REQ filed, it was already a known/flagged gap).
  • The reason useState no longer pre-defaults to 'changed_mind' — it starts empty with a disabled placeholder MenuItem, and the continue CTA stays disabled until a reason is chosen.
  • CancellationPolicyDisclosure is untouched (byte-identical behavior, per the keep-list).

Review flowbookings/[id]/review/page.tsx + the list row:

  • A context-recap card (service name off the variant snapshot, nurse avatar + name, Shamsi visit date — all off the already-cached useBookingDetail, no new fetch) renders above both the eligible-form and the already-reviewed states.
  • The moderation-expectation note («نظر شما پس از بررسی منتشر می‌شود») now renders before submit, not only in the post-submit "under review" state.
  • Fixed the ungated-hook defect: useMyReviewForBooking(bookingId) now passes { enabled: reviewable } (booking?.status === 'completed' || 'closed'), matching the exact gate the booking-detail page already used for the same hook.
  • The list-row star-strip CTA (above) is the "post-completion review nudge" half of this deliverable.

Misc verified defects:

  • BookingRequestSummaryCard's whenLabel bidi-isolated: the date·time-range now wraps the clock-digits segment in a dir="ltr" tabular-nums span, matching SessionCard.tsx's existing precedent exactly. A new co-located test asserts the isolation.
  • The «ادامه پرداخت ←» arrow-in-string CTA (booking.continue_payment) was not touched — confirmed it's phase 12's catalog-wide sweep, per the phase brief's own scope note; only strings genuinely edited this phase had their arrows reconsidered (none needed it).

What is now testable (and exactly how)

  1. From a nurse profile (C3) tap «درخواست رزرو» → C4 shows the sticky nurse card and the 3-step strip. Blur the empty patient select → inline error; the disabled submit lists what's still missing.
  2. Pick a date from the Jalali chip strip (or the calendar-icon popover) and tap «صبح ۸–۱۲» → the time fields fill silently; tap «زمان دلخواه» → the free time fields appear. The address shows a compact text row with a «تغییر» link, never a grid-canvas stand-in.
  3. Submit → C5 shows the countdown ring with «حدود …» framing (switches to ticking digits under 10 min). Tap «انصراف از درخواست» → the dialog's neutral button reads «نه، نگه دار» and keeps the request; the red «بله، انصراف از درخواست» button cancels it.
  4. Leave C5 → /bookings «در انتظار پاسخ» tab shows the pending request with a live mini-countdown and a tab badge; the row deep-links back to C5. Reject a request with a non-gender/coverage reason (dev nurse inbox) → C5's terminal card offers both «درخواست دوباره با زمان دیگر» (C4 reopens fully prefilled, including patient/address) and «پرستاران مشابه» (search prefilled with the same city/gender).
  5. Seed >20 bookings → the active/past tabs load 20, then «نمایش بیشتر» reveals the rest; every row is keyboard-reachable (Tab + Enter) and carries a status-colored accent stripe.
  6. Open an active booking with a session scheduled → the hero reads «ویزیت N · <relative/Shamsi>
  7. Start a cancellation → the reason select is empty (continue disabled until chosen); «تغییر زمان»/«گفتگو با پشتیبانی» open the support dialog pre-linked to the booking; completing the flow shows the unchanged policy disclosure.
  8. Open a completed booking's /bookings row → a star-strip CTA (only when un-reviewed) deep-links to the review page, which shows the service/nurse/date recap and the moderation note before any input.
  9. npm run check and npm run test:ci are green (101/101 suites, 437/437 tests, including the new JalaliDateIntentPicker and BookingRequestSummaryCard bidi tests).

What is mocked / waiting on a real service

Nothing new mocked — bookingRequests/bookings/reviews/refunds/tickets all run real, unchanged from refinement-phase-4, per the phase's own "None introduced" note. Every deliverable is client-side over existing seams; the .ics file is generated entirely in the browser.

Contracts

  • Consumed: services/bookingRequests (b8), services/bookings (b9), services/refunds (b11), services/reviews (b14), services/tickets (b15) — all unchanged.
  • Requested (dev/shared-working-context/frontend/requests/for-backend.md, next free numbers 044045):
    • REQ-044 — a structured nurseRejectionReasonCode on BookingRequestDto. C5's same-nurse-retry gate currently approximates this with a keyword heuristic over the freeform nurseRejectionReason text (documented in-code, rejectionAllowsSameNurseRetry()).
    • REQ-045 — a typed addressSnapshot/variantSnapshot shape on BookingDetailDto in place of the opaque *SnapshotJson strings, whose field names were confirmed to drift across the seed fixtures (city/cityName/cityNameFa, line/addressLine). The client's addressSnapshotLabel() tries every candidate key defensively; no user-facing defect today, just an unenforced contract.

Docs updated

  • client/CLAUDE.md "Project Structure": the /bookings tree (tabs + wired useCustomerRequests + load-more, C4/C5 redesign notes, the cancel/review off-ramp and gating fixes), the new JalaliDateIntentPicker entry, the booking/ composite line (hero, .ics, EVV presence, the StatusTimeline-not-StepperHeader correction — the prior line was already stale before this phase), CountdownTimer's ring now having a live consumer, and BookingRequestSummaryCard's bidi fix.

Follow-ups for later phases

  • List-row EVV presence indicator (phase brief §3.4) was not built — BookingListItemDto has no per-row EVV/session field and an N+1 per-row fetch would be the wrong shape; needs a product/API decision (a lightweight currentlyCheckedIn boolean on the list DTO, or accept the gap) before a future phase attempts it.
  • REQ-044/045 (rejection-reason code, snapshot typing) — no phase currently owns picking these up; flagged for whichever backend phase next touches b8/b9.
  • The arrow-in-string continue_payment CTA sweep stays phase 12's, as scoped.

Memory

Saved a project-type memory (ui_phase_5_booking_lifecycle.md, indexed in MEMORY.md) covering: the lifecycle tabs model (client-side status bucketing over one growing-pageSize query, not per-tab server filters — a deliberate trade-off since the list API takes only a single status), the terminal-state recovery decision tree (same-nurse retry vs similar-nurses, gated by a keyword heuristic pending REQ-044), the confirm-dialog labeling convention (ConfirmDialog cancelLabel=keep/neutral, confirmLabel=destructive/error — apply to every future confirm dialog), the presence-headline pattern, and what phase 6 (checkout) should know about the accepted-request → payment handoff surfaces touched here (C5's payment countdown + continue_payment CTA, unchanged in shape).