frontend phase 14

This commit is contained in:
hamid
2026-07-10 18:46:16 +03:30
parent 85488bc25b
commit bc51cf59b4
73 changed files with 3582 additions and 13 deletions
@@ -456,3 +456,35 @@ delivers fixes in its own change. **Frontend never edits backend code to "fix" a
until they land. **Note:** the wireframe's four-tab E2 record is not in the data model — please confirm whether
the family-owned record is a real MVP entity or a future addition (the client treats it as forward-looking).
- **Status:** open
## REQ-028 — Ticket inbox enrichment (unread + last-activity), message author name, by-booking lookup, optimistic idempotency — filed by frontend-phase-14-b15 — 2026-07-10
- **Need:** Four additions the f14 messaging UI renders that the b15 `TicketSummaryDto`/`TicketThreadDto`/message
routes don't yet expose (the two ticket screens work today because `services/tickets` is **mock-primary**
`USE_TICKETS_MOCK = true`; the real `ticketsClientApi` maps every live b15 route 1:1 and leaves these blank):
1. **`unreadCount` + `lastMessageAt` on `TicketSummaryDto`.** The inbox card shows an **unread indicator** and
sorts/labels by **last activity**, but the wire summary carries only `createdAt` (no unread, no last-message
time). Today the client falls back to `createdAt` and hides the unread badge on the real path; the mock
supplies both. Proposed: `TicketSummaryDto { …, lastMessageAt: string (UTC), unreadCount: int }` (unread =
messages after the caller's `last_read_at`, internal excluded).
2. **A message author display label on `TicketMessageDto`** (or on `TicketParticipantDto`). The thread bubble
labels each message by author, but the DTO carries only `senderId` — no name. The client derives the label
from the participant **role** (`author_customer`/`author_nurse`/`author_support`/`author_system`) and never
shows a raw identity (privacy — the thread is deliberately not a contact directory). Please **confirm this
role-label approach is intended**, or add a **server-masked** display name if a name is wanted. (No phone /
no real name is the safe default.)
3. **A user-facing by-booking ticket lookup/filter.** `GET /tickets` (the user list) filters only by
`Status`/`ReferenceCode` — there's no `bookingId` filter (only the **admin** list has one). The
"Get support" action on a booking is specced to **jump to the existing coordination ticket** if one exists;
without a by-booking read the client can't find it. The mock approximates this via idempotent
`openTicket(category:'coordination', bookingId)` (returns the existing thread). Proposed: add a `bookingId`
query param to `GET /tickets`, or `GET /tickets/by_booking/{bookingId}?category=coordination`.
4. **An idempotency / `clientMessageId` field on `POST /tickets/{id}/messages`.** The optimistic send generates a
`clientMessageId` to reconcile the pending bubble; the server has no field for it, so a retried send (network
flap) could create a duplicate server-side. The client does **not** send it today (reconciliation is
client-only). Proposed: accept an optional `clientMessageId`/`idempotencyKey` on the post-message body and
dedupe on it, echoing it back on `PostMessageResult`.
- **Why:** the f14 inbox (unread + last-activity), the thread (author labels + jump-to-coordination), and the
optimistic composer render these; the domain is mock-primary precisely because (1)/(3) aren't served and the
linked bookings are themselves mock-primary. When they land the swap is a single `USE_TICKETS_MOCK = false` flip
(no hook/component change) — `ticketsClientApi` already maps the live routes.
- **Status:** open