frontend phase 14
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user