ui phase 7

This commit is contained in:
hamid
2026-07-19 11:56:59 +03:30
parent a438edeeaa
commit edc38543fd
39 changed files with 1538 additions and 208 deletions
@@ -132,6 +132,11 @@ function toListItem(dto: BookingRequestDto, role: RequestRole): BookingRequestLi
nurseResponseDeadlineAt: dto.nurseResponseDeadlineAt,
paymentDeadlineAt: dto.paymentDeadlineAt,
customerNotes: role === 'nurse' ? dto.customerNotes : null,
// ui-phase-7 (REQ-050): the nurse inbox is decision-first — service + price on the row itself. The
// real list DTO doesn't carry these yet; only the mock stamps them (the detail DTO always has them).
variantLabel: dto.variantLabel,
variantPrice: dto.variantPrice,
variantPriceUnit: dto.variantPriceUnit,
};
}
@@ -0,0 +1,18 @@
const MINUTES_PER_HOUR = 60;
/**
* Humanized minutes-remaining copy above `CountdownTimer`'s coarse threshold («حدود ۳ ساعت» / «حدود ۲۵
* دقیقه»). Shared by the customer C5 response countdown and the nurse inbox's urgency-tinted countdown
* pill (ui-phase-7) — both count down the same `nurseResponseDeadlineAt`, so the humanized framing must
* read identically in both places. `t` is the `booking` namespace translator (`countdown_about_hours`/
* `countdown_about_minutes`).
*/
export function coarseResponseLabel(
minutes: number,
t: (key: string, values?: Record<string, number>) => string,
): string {
if (minutes >= MINUTES_PER_HOUR) {
return t('countdown_about_hours', { hours: Math.round(minutes / MINUTES_PER_HOUR) });
}
return t('countdown_about_minutes', { minutes });
}
@@ -13,15 +13,21 @@ import type { BookingRequestStatus } from '../types';
/**
* The nurse incoming-requests inbox (`list?role=nurse`), defaulting to `pending_nurse_response`. Lightly
* polls so newly-arrived requests appear without a manual refresh; every accept/reject invalidates this
* list so an actioned request leaves the pending inbox immediately.
* list so an actioned request leaves the pending inbox immediately. `options.enabled` (default `true`)
* lets a multi-tab inbox (ui-phase-7) mount every status query up front without polling tabs the nurse
* isn't currently viewing.
*/
export function useNurseRequestInbox(status: BookingRequestStatus | undefined = 'pending_nurse_response', page = 1) {
export function useNurseRequestInbox(
status: BookingRequestStatus | undefined = 'pending_nurse_response',
page = 1,
options?: { enabled?: boolean },
) {
const isAuthenticated = useIsAuthenticated();
const params = { role: 'nurse' as const, status, page, pageSize: BOOKING_REQUEST_PAGE_SIZE };
return useQuery({
queryKey: bookingRequestKeys.nurseInbox(params),
queryFn: () => bookingRequestsApi.list(params),
enabled: isAuthenticated,
enabled: isAuthenticated && (options?.enabled ?? true),
staleTime: BOOKING_REQUEST_STALE_TIME,
gcTime: BOOKING_REQUEST_GC_TIME,
refetchInterval: BOOKING_REQUEST_POLL_MS,
@@ -129,6 +129,14 @@ export interface BookingRequestListItem {
paymentDeadlineAt: string | null;
/** Nurse view only (stage-1 plaintext); `null` in the customer inbox. */
customerNotes: string | null;
/**
* Client-augmented (ui-phase-7, REQ-050) — the list DTO carries no variant fields today. `undefined` on
* the real path until the field lands; the nurse-inbox card renders the decision-first headline when
* present and degrades to the patient-name headline otherwise (mock-tolerant, never fetched per-row).
*/
variantLabel?: string | null;
variantPrice?: string | null;
variantPriceUnit?: PriceUnit | null;
}
/** The `booking_requests/create` body (contract). Money-free; ids come from search/patients/addresses. */