26 KiB
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 contractreviews-records.md· Unlocks: (last vertical-feature frontend phase — the support/notification surfaces infrontend-phase-14-b15.mdreuse 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), theservices/{domain}+ TanStack Query caching pattern (copy theauthservice 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 (areviewsnamespace was reserved in f0 — fill it; add arecordsnamespace). - 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 bookingservicesdomain — 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.mdproduced 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.mdand../_shared/frontend-conventions-checklist.md— how you work and the tick-list you are graded on.../../../client/CLAUDE.mdin full — the RSC/client boundary, theservices/{domain}+ Query rules, i18n, theme/tokens, cookies. Non-negotiable.- Invoke the
frontend-designerskill before any visual work. It is the design/brand contract (palette: teal#1d4a40, terracotta#d98c6afor the nurse-view E3 accent, cream; tokens, typography, theApp*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.md— the b14 contract you consume. Read it end-to-end for exact request/response shapes, routes, status codes, thereview_statusenum (pending_moderation/published/hidden/rejected), the care-record tab/section shape, and which clinical fields are masked vs. full. Derive yourtypes.tsfrom this, not from guesses.../../contracts/conventions/api-conventions.md+money-and-types.md— envelope,snake_caseroutes/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_moderationdefault, 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.md—reviews(1:1 booking, rating 1–5 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.html— Section 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 exactservices/{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.ts—Review(id,booking_id,nurse_id,customer_display_name,rating1–5,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 fromswagger.json.keys.ts— a query-key factory:reviews.nurse(nurseId, page),reviews.eligibility(bookingId),reviews.myReviewForBooking(bookingId).apis/clientApi.ts— wrapclientFetch: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). AserverApi.tsonly 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(useInfiniteQueryor pageduseQuerywithselectfor the aggregate slice),useReviewEligibility,useCreateReview(useMutation→ on successinvalidateQueriesforreviews.eligibility(bookingId)andreviews.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 ispending_moderationand must not appear publicly).index.tsbarrel.
3.2 services/patientRecords domain
Same shape. Consume the b14 contract. Patient-scoped, not booking-scoped.
types.ts—CareRecordTab = '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_at— read- 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.ts—records.patient(patientId),records.history(patientId, page),records.access(patientId).apis/clientApi.ts—getPatientCareRecord(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 append —POST .../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 → invalidaterecords.patient),useCreateVisitNote(nurse mutation → invalidaterecords.historyandrecords.patient; the nurse cannot callupdateCareRecord— don't even wire that hook into the nurse view).index.tsbarrel.
3.3 Screens & flows
(a) Leave-a-review flow (customer; entry from completed booking detail / completed-bookings list)
- A
<LeaveReviewSheet>(or page) reached only whenuseReviewEligibility(bookingId).can_reviewis true and the booking status is completed/closed. Contains: a 1–5 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 offtag_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
publishedreviews viauseNurseReviews, 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 renderpending_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 itsaggregate_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.
- 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
(
- 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
CareTaskas 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, noupdateCareRecordhook 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 fromfrontend-phase-0.md: all data goes throughclientFetch/serverFetchinservices/reviewsandservices/patientRecords. No rawfetch(). - If the b14 contract
reviews-records.md(or theswagger.jsonsnapshot) is not yet published when you run, build a mockclientApibehind 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 anif (mock)in a component — and:- append the missing/uncertain shapes to
../../shared-working-context/frontend/requests/for-backend.md(per operating-rules §6), and - record the mock in your phase report + the
mocks-registry.mdso it swaps out cleanly once the real endpoint lands.
- append the missing/uncertain shapes to
- No third-party client seam is introduced here (AI moderation
IReviewModerationServiceand field encryptionIFieldEncryptorare 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(orhidden/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
updateCareRecordor 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
403from 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]; nonext/headers/next-intl/serverin client components. SetqueryKey/staleTimedeliberately and invalidate on every mutation (review create → eligibility/my-review; note append → history+record; record edit → record) so nothing over-fetches. Useselectfor the aggregate/tab slices to avoid needless re-renders. Every string is a key in bothen.jsonandfa.json;fadefault & RTL; colours fromtokens.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/reviewsandservices/patientRecordsexist with the f0 shape (types/keys/apis/hooks/index); types are derived from the published b14 contract (or mocked behind the seam with afor-backend.mdentry), 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 checkand (if a shared component changed)npm run test:ciare green;en.json/fa.jsonin sync (reviews+recordsnamespaces). client/CLAUDE.mdProject Structure updated for the two new service domains + any new shared component/route; thefrontend-designerskill 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).
- 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. - 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.
- 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.
- 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.
- Gate checks:
npm run checkgreen;npm run test:cigreen for the new shared components; toggling locale flipsdir/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)
- Docs: update the Project Structure tree in
../../../client/CLAUDE.mdforservices/reviews,services/patientRecords, and any new shared component/route; note thereviews/recordsi18n namespaces. If you discovered a business-rule detail the product docs don't capture (e.g. an exact masking behaviour), record it in../../../product/business/11-reviews-trust-and-safety.mdor../../../product/data-model/10-reviews-and-records.md— don't invent rules. - Contract: consume
reviews-records.md(b14) as the source of truth for every shape. The frontend does not write contracts — if a shape is missing, wrong, or unmasked when it should be masked, append a request to../../shared-working-context/frontend/requests/for-backend.mdand mock behind theservices/{domain}seam meanwhile. - Handoff & report: append your phase summary to
../../shared-working-context/frontend/STATUS.md; write../../shared-working-context/reports/frontend-phase-13-report.md(what was built, what is testable and exactly how per §7, what is mocked client-side + how it swaps, contracts consumed, follow-ups — e.g. the deferred moderation UI for f15); update../../shared-working-context/reports/mocks-registry.mdfor any client-side mock you used. - Memory: save a
project-type memory note for the non-obvious decisions this phase locks in (review is published-only on the client and never optimistically injected; the patient record is family-owned and patient-scoped with the nurse strictly append-only; access-denied is a first-class state), with a one-line pointer inMEMORY.md.