refinement phase 4
This commit is contained in:
@@ -21,7 +21,7 @@ import type {
|
||||
|
||||
const API = '/api/v1';
|
||||
|
||||
/** Wire `TicketSummaryDto` (camelCase, per api-conventions). */
|
||||
/** Wire `TicketSummaryDto` (camelCase). REQ-028 (delivered) added `lastMessageAt`/`unreadCount`. */
|
||||
interface TicketSummaryWire {
|
||||
id: number;
|
||||
referenceCode: string;
|
||||
@@ -31,6 +31,8 @@ interface TicketSummaryWire {
|
||||
bookingId: number | null;
|
||||
refundId: number | null;
|
||||
createdAt: string;
|
||||
lastMessageAt: string | null;
|
||||
unreadCount: number;
|
||||
}
|
||||
|
||||
/** Wire `TicketMessageDto`. `isInternal` is present on the DTO but is `false` in the user view (server-stripped). */
|
||||
@@ -67,6 +69,9 @@ function mapSummary(w: TicketSummaryWire): TicketSummary {
|
||||
bookingId: w.bookingId,
|
||||
refundId: w.refundId,
|
||||
createdAt: w.createdAt,
|
||||
// REQ-028 (delivered): the inbox unread badge + last-activity sort now come off the wire.
|
||||
lastMessageAt: w.lastMessageAt,
|
||||
unreadCount: w.unreadCount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -162,9 +167,11 @@ function mapAdminThread(w: TicketThreadWire, viewerUserId?: number): AdminTicket
|
||||
* - `openTicket` → `POST /tickets`.
|
||||
* - `postMessage` → `POST /tickets/{id}/messages` (a non-staff caller never sets `isInternal`).
|
||||
*
|
||||
* NOT the primary implementation this phase (`USE_TICKETS_MOCK = true`) — see `constants.ts`. The wire
|
||||
* summary has no `unreadCount`/`lastMessageAt` (REQ-028), so those stay undefined here (the inbox degrades).
|
||||
* `clientMessageId` is client-only (optimistic reconcile) — not sent (the server has no field for it yet).
|
||||
* PRIMARY once `USE_TICKETS_MOCK = false` (refinement-phase-4; REQ-028 delivered): the summary now carries
|
||||
* `unreadCount`/`lastMessageAt` (inbox badge + last-activity sort) and the message post sends the optimistic
|
||||
* `clientMessageId` (server dedupes + echoes it back). The user list still filters only by `Status`; the
|
||||
* "jump to the existing coordination ticket" by-booking lookup is a minor follow-up (REQ-028 #3 —
|
||||
* `GET /tickets?BookingId=` is served, but no client method targets it yet).
|
||||
*/
|
||||
export const ticketsClientApi: TicketsApi = {
|
||||
listMyTickets: async (params: TicketListParams): Promise<Paginated<TicketSummary>> => {
|
||||
@@ -202,7 +209,9 @@ export const ticketsClientApi: TicketsApi = {
|
||||
unwrap(
|
||||
await clientFetch<ApiEnvelope<PostMessageResult>>(`${API}/tickets/${ticketId}/messages`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ body: body.body }),
|
||||
// REQ-028 (delivered): send the optimistic `clientMessageId` so the server dedupes a retried send
|
||||
// and echoes it back on `PostMessageResult` for reconciliation.
|
||||
body: JSON.stringify({ body: body.body, clientMessageId: body.clientMessageId }),
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -233,12 +242,17 @@ export const ticketsClientApi: TicketsApi = {
|
||||
return mapAdminThread(wire, viewerUserId);
|
||||
},
|
||||
|
||||
// Staff post — may set `isInternal` (the one caller allowed to). `clientMessageId` stays client-only.
|
||||
// Staff post — may set `isInternal` (the one caller allowed to). REQ-028: send `clientMessageId` too.
|
||||
postAdminMessage: async (ticketId: number, body: PostAdminMessageRequest): Promise<PostMessageResult> =>
|
||||
unwrap(
|
||||
await clientFetch<ApiEnvelope<PostMessageResult>>(`${API}/tickets/${ticketId}/messages`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ body: body.body, isInternal: body.isInternal }),
|
||||
body: JSON.stringify({
|
||||
body: body.body,
|
||||
isInternal: body.isInternal,
|
||||
clientMessageId: body.clientMessageId,
|
||||
}),
|
||||
}),
|
||||
),
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user