Files
baya-monorepo/dev/phases/frontend/frontend-phase-13-b14.md
T
2026-06-28 21:59:59 +03:30

26 KiB
Raw Blame History

Frontend Phase 13 (b14) — Reviews & patient care records

Mission: close the trust loop and the continuity-of-care loop in the client. After a visit is completed, the family leaves one moderated review that surfaces on the nurse's public profile only once it clears moderation; and the family-owned, patient-scoped care record becomes a real screen — the customer reads/edits it (داروها/روتین/سوابق/وظایف) under an ownership banner, while the assigned nurse may only append a visit note (the EVV check-in/out itself already shipped in f8). This is the brand-survival surface: vulnerable patients are cared for unobserved at home, so we never render unmoderated content publicly, we never let a nurse edit the family's record, and we treat clinical fields as sensitive.

Track: frontend · Depends on: frontend-phase-8-b9.md (booking detail · sessions · EVV) + the b14 contract reviews-records.md · Unlocks: (last vertical-feature frontend phase — the support/notification surfaces in frontend-phase-14-b15.md reuse these patterns) Before you start, read ../_shared/agent-operating-rules.md. It is not optional.


1. Context — where this sits

This is the last feature-domain frontend phase before the support/admin consoles. The booking lifecycle is fully built: a customer can search, request, get accepted, pay (escrow / BNPL), and the nurse runs the visit with EVV. The two things still missing on the client are the post-visit review (what makes the marketplace's rating signal real) and the patient care record viewer/authoring (what makes continuity-of-care real across nurse changes). Both are described in the wireframe's Section E (E1/E2 patient record, E3 visit note) and Section C (the review snippet on the nurse profile C3). You implement the family-facing review + record screens and the nurse-facing append-only visit-note part of E3.

What already exists (do not rebuild) — confirmed in the codebase + prior handoffs:

  • The whole frontend foundation from frontend-phase-0.md: the three actor shells (customer mobile + 5-tab bottom nav خانه/رزروها/بیماران/کیف‌پول/پروفایل, nurse, admin), the services/{domain} + TanStack Query caching pattern (copy the auth service shape: types.ts/keys.ts/apis/clientApi.ts/hooks/use*.ts/index.ts), the contracts→types pattern, the shared composite components (status chip, stepper, cards), the money/format util, and the i18n namespace baseline (a reviews namespace was reserved in f0 — fill it; add a records namespace).
  • Booking detail, sessions & EVV from frontend-phase-8-b9.md: the booking-detail screen, the status timeline, the nurse EVV check-in/check-out banner and the post-confirmation care-instructions surface, and the booking status enum (the completed/closed state that gates a review). Reuse the booking-detail screen and the booking services domain — the "Leave a review" entry point hangs off a completed booking; the visit-note authoring is the note + task-checklist part of E3 that sits below the EVV banner f8 already built. Do not rebuild EVV.
  • The patients list & a patient's identity from frontend-phase-2-b3.md: E1 (patients list, Patients tab) and the patient header (name, age/gender, conditions) already exist — the record viewer E2 is a new screen reached from a patient; reuse the patient header, don't re-fetch the patient identity from scratch.
  • The nurse public profile (C3) from frontend-phase-6-b7.md: it already renders avatar/badges/services and a single review snippet. This phase adds the reviews tab to that existing profile — extend it, don't fork it.
  • The contract reviews-records.md produced by backend phase b14 — the source of truth for every shape below. If it is not yet published, mock behind the seam (§4) and file the gap (§8).

2. Required reading (do this first)

  • ../_shared/agent-operating-rules.md and ../_shared/frontend-conventions-checklist.md — how you work and the tick-list you are graded on.
  • ../../../client/CLAUDE.md in full — the RSC/client boundary, the services/{domain} + Query rules, i18n, theme/tokens, cookies. Non-negotiable.
  • Invoke the frontend-designer skill before any visual work. It is the design/brand contract (palette: teal #1d4a40, terracotta #d98c6a for the nurse-view E3 accent, cream; tokens, typography, the App* library, layout shells, the hard UI rules). Every screen in this phase goes through it — the star input, the tag chips, the tabbed record viewer, the ownership banner, the visit-note composer.
  • reviews-records.mdthe b14 contract you consume. Read it end-to-end for exact request/response shapes, routes, status codes, the review_status enum (pending_moderation/published/hidden/rejected), the care-record tab/section shape, and which clinical fields are masked vs. full. Derive your types.ts from this, not from guesses.
  • ../../contracts/conventions/api-conventions.md + money-and-types.md — envelope, snake_case routes/JSON, pagination (page/page_size, items+total), enums-as-codes (mirror as string-literal unions, never hardcode a label off a code), UTC + Shamsi display is a client concern, and the PII/sensitive-field rule (clinical notes are encrypted-at-rest, returned only to authorized callers, sometimes masked — the two-stage clinical-disclosure rule applies).
  • ../../../product/business/11-reviews-trust-and-safety.md — the business rules: one review per completed booking, pending_moderation default, recompute-on-every- transition (a server concern, but it means a hidden review must vanish from the profile — your cache must invalidate), low-rating → support alert (server-side; you just render the "under review" state).
  • ../../../product/data-model/10-reviews-and-records.mdreviews (1:1 booking, rating 15 CHECK, body, moderation status), review_tags_master/ review_tag_links (the tag vocabulary), patient_care_records (nurse-authored, patient-scoped not booking-scoped, encrypted, strict access: owning customer + nurse with a confirmed booking for that patient + admin).
  • ../../../product/wireframes/index.htmlSection E (E1 patients list, E2 patient record with the four tabs + ownership banner "این پرونده متعلق به خانواده است …", E3 visit note in the terracotta "نمای پرستار" frame: EVV banner [already built] + today's task checklist [give متفورمین, measure blood pressure, short walk] + free-text visit-note field) and Section C (C3 nurse profile with the latest-review snippet you turn into a tab).
  • The existing client/src/services/auth/* — the exact services/{domain} shape to copy, and the booking + patients services from f8 / f2 you will reuse.

3. Scope — build this

Two new services/{domain} domains (reviews, patientRecords), their hooks, and four wireframe screens (+ one tab added to an existing screen). Every screen is RTL-first, fa default, both locales in sync, colours from tokens, MUI v9 primitives reused, query-cached with deliberate keys and invalidate-on-mutation. Invoke the frontend-designer skill for each screen.

3.1 services/reviews domain

Copy the auth service shape. Consume the b14 contract.

  • types.tsReview (id, booking_id, nurse_id, customer_display_name, rating 15, body, status: 'pending_moderation' | 'published' | 'hidden' | 'rejected', tag_codes: string[], created_at), NurseReviewsResponse (items, total, aggregate_rating, review_count), ReviewTag (code, plus the i18n key is the client's, not a label off the wire), CreateReviewRequest (booking_id, rating, body, tag_codes), ReviewEligibility (can_review: boolean, reason?). Mirror the exact wire shape/casing from swagger.json.
  • keys.ts — a query-key factory: reviews.nurse(nurseId, page), reviews.eligibility(bookingId), reviews.myReviewForBooking(bookingId).
  • apis/clientApi.ts — wrap clientFetch: getNurseReviews(nurseId, page) (GET .../get_nurse_reviews, published only — the server already filters; never request or render other statuses publicly), getReviewEligibility(bookingId), createReview(body) (POST .../create_review). A serverApi.ts only if the nurse-profile reviews tab is prefetched in the RSC (prefer it — removes a client round-trip on C3).
  • hooks/ (one per file): useNurseReviews (useInfiniteQuery or paged useQuery with select for the aggregate slice), useReviewEligibility, useCreateReview (useMutation → on success invalidateQueries for reviews.eligibility(bookingId) and reviews.myReviewForBooking(bookingId) so the booking-detail CTA flips to the "under review" state immediately; do not optimistically push the review into the public nurse list — it is pending_moderation and must not appear publicly).
  • index.ts barrel.

3.2 services/patientRecords domain

Same shape. Consume the b14 contract. Patient-scoped, not booking-scoped.

  • types.tsCareRecordTab = 'medications' | 'routine' | 'history' | 'tasks'; Medication (name, frequency, timing_note), RoutineItem, HistoryEntry, CareTask (label, done), VisitNote (id, booking_id, nurse_display_name, body, task_results, created_atread- only/append-only from the client's perspective), PatientCareRecord (the family-owned editable record: medications/routine/tasks the customer maintains), RecordAccess (can_view, can_edit, can_append_note, denied_reason?), CreateVisitNoteRequest (booking_id, body, task_results). Clinical fields are sensitive — treat masked/full per the contract; never log them.
  • keys.tsrecords.patient(patientId), records.history(patientId, page), records.access(patientId).
  • apis/clientApi.tsgetPatientCareRecord(patientId) (GET .../get_patient_care_record, the four-tab payload), getPatientHistory(patientId, page) (longitudinal visit-note history, paged), updateCareRecord(patientId, body) (customer edits — medications/routine/tasks), createVisitNote(patientId, body) (nurse appendPOST .../create_visit_note). The access check rides on the read responses (403 from the envelope → render access-denied, don't crash).
  • hooks/usePatientCareRecord, usePatientHistory (paged/infinite), useUpdateCareRecord (customer mutation → invalidate records.patient), useCreateVisitNote (nurse mutation → invalidate records.history and records.patient; the nurse cannot call updateCareRecord — don't even wire that hook into the nurse view).
  • index.ts barrel.

3.3 Screens & flows

(a) Leave-a-review flow (customer; entry from completed booking detail / completed-bookings list)

  • A <LeaveReviewSheet> (or page) reached only when useReviewEligibility(bookingId).can_review is true and the booking status is completed/closed. Contains: a 15 star input (a new shared <RatingInput> composite — see §3.4), a multiline body field, and tag chips (multi-select from the contract's tag vocabulary; chip labels are i18n keys keyed off tag_codes, never off the wire). Primary action "ثبت نظر".
  • States: not-eligible → CTA hidden/disabled with a clear reason ("نظر فقط برای ویزیت‌های تکمیل‌شده امکان‌پذیر است"); eligible → the form; submitting; submitted → an "در حال بررسی" / "under review" banner (the review is pending_moderation, not yet public) and the CTA becomes a passive "نظر شما ثبت شد و در حال بررسی است"; already-reviewed (1:1) → show the existing pending/published state, never a second form; error → domain 4xx message, preserve the draft.

(b) Nurse public-profile reviews tab (customer; on the existing C3 nurse profile)

  • Add a reviews tab to the existing nurse-profile screen. Render only published reviews via useNurseReviews, with the aggregate rating + review count header, paginated/infinite list, each row: stars, body, tag chips, masked customer display name, Shamsi date. States: loading (skeleton), empty ("هنوز نظری ثبت نشده"), error. Never render pending_moderation/hidden/rejected — if a review is hidden server-side, the next fetch simply omits it (the aggregate recompute is the server's job; the client just trusts the published list and its aggregate_rating).

(c) Patient record viewer E2 (customer; reached from a patient in the Patients tab / E1)

  • Header (reuse the f2 patient header: name, age/gender, condition chips, an ویرایش/edit affordance)
    • a tabbed body: داروها (Medications) [default], روتین (Routine), سوابق (History), وظایف (Tasks). Medication cards (drug, frequency, timing/notes). The سوابق tab shows the longitudinal visit-note history (§(e)). A persistent ownership banner: "این پرونده متعلق به خانواده است" (the record belongs to the family). Customer can edit medications/routine/tasks (useUpdateCareRecord); the سوابق (nurse visit notes) are read-only to everyone.
  • States: loading (skeleton per tab), empty per tab ("دارویی ثبت نشده" / "یادداشتی ثبت نشده"), access-denied (403 → a clear, non-leaking "شما به این پرونده دسترسی ندارید" card — never show partial clinical data), error.

(d) Nurse visit-NOTE authoring E3 — the note + task-checklist part only (nurse; terracotta "نمای پرستار" frame, on the booking-visit screen f8 built)

  • Below the EVV check-in/out banner f8 already renders, add: today's task checklist (from the patient's care tasks — render each CareTask as a checkbox the nurse ticks: give متفورمین, measure blood pressure, short walk) and a free-text visit-note field. Primary action "ثبت یادداشت" (useCreateVisitNote). The nurse view is append-only: it must never expose the customer's edit affordances (no medication/routine/tasks editing, no updateCareRecord hook wired) — the nurse can read prior history for continuity and append a note, nothing more.
  • States: append form (default), submitting, saved (note appended → it appears in the longitudinal history), error (preserve draft). If the nurse lacks a confirmed booking for that patient (access.can_append_note === false), hide the composer.

(e) Longitudinal patient history (customer in the سوابق tab + nurse for continuity)

  • A patient-scoped, paginated visit-note timeline (usePatientHistory): each entry = nurse display name, Shamsi date, note body, completed-task summary — ordered newest-first. It persists across nurse changes (patient-scoped, so a new nurse reads it before/at the visit). Read-only. States: loading/empty/error.

(DEFERRED) — do not build in this phase: review moderation UI (the admin approve/hide/reject queue → frontend-phase-15-b15.md); two-way (nurse-reviews-customer) reviews; structured tag aggregation dashboards ("% punctual") — render the tag chips, but the aggregate analytics are deferred per the product doc; the in-app "raise a concern" flag and emergency banner → frontend-phase-14-b15.md. Tag those entry points with a pointer if a placeholder is unavoidable; otherwise leave them out.

4. Mocks & seams in this phase

  • Reuse the services/{domain} seam pattern from frontend-phase-0.md: all data goes through clientFetch/serverFetch in services/reviews and services/patientRecords. No raw fetch().
  • If the b14 contract reviews-records.md (or the swagger.json snapshot) is not yet published when you run, build a mock clientApi behind the same domain seam returning real-shaped fixtures (a completed-booking eligibility, a small published- review list with an aggregate, a four-tab care record, an append-able history) — selected by config, never an if (mock) in a component — and:
    1. append the missing/uncertain shapes to ../../shared-working-context/frontend/requests/for-backend.md (per operating-rules §6), and
    2. record the mock in your phase report + the mocks-registry.md so it swaps out cleanly once the real endpoint lands.
  • No third-party client seam is introduced here (AI moderation IReviewModerationService and field encryption IFieldEncryptor are server-side b14 concerns — the client never sees plaintext-vs- ciphertext, only the authorized/masked payload).

5. Critical rules you must not get wrong

  • Review eligibility is gated on a completed/closed booking. The "Leave a review" CTA is enabled only when the booking status is completed/closed (from f8's booking enum) and the server says can_review. Never offer a review for a cancelled/expired/other-customer booking, and enforce one review per booking (1:1) — if a review already exists, show its state, never a second form.
  • Never render pending_moderation (or hidden/rejected) content publicly. The nurse-profile reviews tab requests and renders published only. After a user submits, show an "under review" state locally — do not optimistically inject the new review into any public list or aggregate. Trust the server's published list + aggregate_rating; when a review is hidden server-side, invalidate and re-fetch rather than mutating the count yourself.
  • The patient care record is FAMILY-OWNED and PATIENT-scoped. The customer owns and edits it (medications/routine/tasks); the record persists across nurse changes because it is keyed to the patient, not the booking. Render the ownership banner "این پرونده متعلق به خانواده است" on E2.
  • The nurse can ONLY append a visit note — never edit the record. The nurse view exposes the task checklist + a note composer and the read-only history; it must not wire updateCareRecord or any medication/routine/task editing. Append-only is a hard boundary, not just a hidden button.
  • Strict access; surface access-denied clearly. Only the owning customer, a nurse with a confirmed booking for that patient, and admin may view a record. A 403 from the envelope → render a clear, non-leaking access-denied card (no partial clinical data), never a crash or a blank tab.
  • Clinical fields are sensitive. Treat masked vs. full strictly per the contract (two-stage clinical disclosure spirit); never log clinical text, never persist it to localStorage, never put it in a query string.
  • RSC/client boundary, caching, re-renders, i18n, RTL, tokens. No layout above [locale]; no next/headers/next-intl/server in client components. Set queryKey/staleTime deliberately and invalidate on every mutation (review create → eligibility/my-review; note append → history+record; record edit → record) so nothing over-fetches. Use select for the aggregate/tab slices to avoid needless re-renders. Every string is a key in both en.json and fa.json; fa default & RTL; colours from tokens.css (terracotta accent for the nurse E3 frame via tokens, never hardcoded); MUI v9 primitives reused; Shamsi date display is the client's job.

6. Definition of Done

The shared definition-of-done.md, plus:

  • services/reviews and services/patientRecords exist with the f0 shape (types/keys/apis/ hooks/index); types are derived from the published b14 contract (or mocked behind the seam with a for-backend.md entry), never guessed.
  • The leave-a-review flow enforces completed-booking eligibility + 1:1, shows the under-review state on submit, and never injects unmoderated content into a public list.
  • The nurse-profile reviews tab renders published-only with aggregate rating + count, paginated, with loading/empty/error states.
  • The E2 patient record viewer renders the four tabs (داروها/روتین/سوابق/وظایف), the ownership banner, customer edit of medications/routine/tasks, and a clear access-denied state on 403.
  • The nurse E3 visit-note authoring (task checklist + note composer) is append-only, sits below the f8 EVV banner, exposes no record-editing affordances, and the appended note appears in the longitudinal history.
  • The longitudinal history is patient-scoped, paginated, read-only, newest-first, and persists across nurse changes.
  • New shared composites (<RatingInput>, the tag-chip selector, the tabbed record viewer if reused) live at the right shared level with co-located *.test.tsx; npm run check and (if a shared component changed) npm run test:ci are green; en.json/fa.json in sync (reviews + records namespaces).
  • client/CLAUDE.md Project Structure updated for the two new service domains + any new shared component/route; the frontend-designer skill was invoked for the visual work.

7. How to test (what a human can verify after this phase)

Run npm run dev (and have the b14 backend reachable, or the seam mock active).

  1. Leave a review on a completed booking → pending → appears after moderation. As a customer on a completed booking, open "ثبت نظر", give 4 stars + body + a tag chip, submit → the screen shows the "در حال بررسی / under review" state and the CTA does not offer a second review. The review does not appear on the nurse's profile yet. After the admin publishes it (b14/f15 path, or flip the mock to published), it appears on the nurse profile reviews tab and the aggregate rating/count updates on next fetch. Confirm a cancelled booking shows no review CTA.
  2. View a patient record with tabs + ownership banner. From the Patients tab, open a patient → E2 shows the four tabs, medication cards under داروها, the "این پرونده متعلق به خانواده است" banner, and the customer can edit a medication/routine/task and see it persist (cache invalidates, no full reload). Visiting a patient you don't own returns the access-denied card, not a crash.
  3. A nurse appends a visit note (cannot edit the record). As the assigned nurse on today's visit (E3, terracotta frame), below the EVV banner: tick the task checklist, write a note, "ثبت یادداشت" → the note saves and shows in the history. Confirm there is no medication/routine/task edit control anywhere in the nurse view.
  4. History persists across nurse changes. The سوابق tab (customer) and the nurse's continuity view show the full patient-scoped, newest-first visit-note timeline — including notes from a different nurse — paginated.
  5. Gate checks: npm run check green; npm run test:ci green for the new shared components; toggling locale flips dir/strings; the reviews tab/list and the record edit show query caching + invalidation in React Query Devtools (no needless refetch).

8. Hand off & document (close the phase)