import type { CalendarEventStatus } from '../calendar'; import type { NotesPanelNote } from '../notes-panel'; /** A fixed phone-label vocabulary, so the label can be localised. The * platform's `DoctorPatientPhoneNumber.label` is free text: the host maps * it onto these three. */ export type AppointmentPhoneLabel = 'mobile' | 'home' | 'work'; /** One labelled number: platform pop-over `phoneNumbers[]` * (`getPrefixAndNumber()`, already masked by `PrivacyMaskedService` when * the user hides patient details). */ export interface AppointmentPhoneNumber { label: AppointmentPhoneLabel; number: string; } /** One highlighted anamnesis answer, as the platform's details dialog lists * it: the question, yes or no, the free answer and the interview date. * Display strings, localised by the host. */ export interface AppointmentAnamnesisAnswer { question: string; yes: boolean; answer?: string; interviewIso: string; } /** * The patient as the details read carries them. Only `id` and `name` are * required. An absent field prints no row, never "none on file". Masking is * the host's, server-side. */ export interface AppointmentDetailsPatient { /** Platform `Patient.patientId`. */ id: string; /** Platform `Patient.name`, the display name. */ name: string; /** Profile photo URL. Without one, the avatar shows initials. */ avatarSrc?: string; /** Date of birth, 'YYYY-MM-DD': a local calendar day, never an instant. */ dobIso?: string; /** The mobile, when `phones` is absent. */ phone?: string; /** Every labelled number (platform pop-over `phoneNumbers`). */ phones?: AppointmentPhoneNumber[]; /** Platform pop-over `emailAddress`, masked server-side when the user * hides patient details. */ email?: string; /** Street: platform `Patient.street`. */ address?: string; /** Platform `Patient.postcode`. */ postalCode?: string; /** Platform `Patient.city`. */ city?: string; /** Platform pop-over `highlightedAnamnesisAnswers`, a COUNT * (`Patient::getNumberOfHighlightedAnamnesisAnswers`). */ highlightedAnamnesisCount?: number; /** Platform pop-over `expiredAnamnesisCount` (`CountExpiredService`). */ expiredAnamnesisCount?: number; /** The highlighted answers themselves. The pop-over only counts them; * the platform's details dialog lists them. */ highlightedAnamnesisAnswers?: AppointmentAnamnesisAnswer[]; } /** The platform's six `recurrence_frequency` values. */ export type AppointmentRecurrenceFrequency = 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'semiannual' | 'yearly'; /** How signing was requested: platform * `DoctorAppointment.requiredObjectsSignMode` `via_sms` or `via_email`. */ export type AppointmentSignChannel = 'sms' | 'email'; /** The form fields an edit can touch (the platform's audit vocabulary). */ export type AppointmentHistoryField = 'patient' | 'type' | 'care' | 'operator' | 'chair' | 'colour' | 'time' | 'duration'; /** * One value an edit changed, before and after, so the history can say * exactly what happened. The kit formats status (platform * `DoctorAppointment.state`, the same six codes), time (`date`) and * duration (`duration`, minutes) itself. `fromIso` / `toIso` take the * appointment's zone-less wall clock, like `appointment.startIso`, or an * instant with a zone; both show in the reader's local time. Every other * field arrives as the host's display text, resolved when the history is * read so it follows the reader's language and patient permissions: * patient (`doctorPatient`), type (`type`: in practice or telehealth), * care (the appointment's name, which the editor edits as care), operator * (`doctorOperator`), chair (`chairId`, by name). Colour (`doctorColor`) * has no name on the platform, only hex values: send the hex and the kit * shows a swatch. A side is absent when the field was empty: first set (a * status set for the first time has no `from`), or cleared. * * `changes` may cover only some of an edit's `fields`: the rest still show, * named without values. In privacy mode the kit shows a patient or care * change by its field name only. * * The platform does not record these yet: `doctor_action` stores which * action ran, who and when (its `details` column is never written for * appointments), so today's entries carry no changes and the kit shows * what it has. */ export type AppointmentHistoryChange = { field: 'status'; from?: CalendarEventStatus; to?: CalendarEventStatus; } | { field: 'time'; fromIso?: string; toIso?: string; } | { field: 'duration'; fromMin?: number; toMin?: number; } | { field: Exclude; from?: string; to?: string; }; /** One history line: who did what to the appointment, and when. `atIso` is * an instant, with a zone. A change entry may list further `changes` made * in the same save (a drop onto another chair's column moves the time and * the chair at once). */ export type AppointmentHistoryEntry = { id: string; atIso: string; actor: string; } & ({ kind: 'created'; } | { kind: 'moved'; fromIso: string; toIso: string; changes?: AppointmentHistoryChange[]; } | { kind: 'resized'; fromMin: number; toMin: number; changes?: AppointmentHistoryChange[]; } | { kind: 'status'; from: CalendarEventStatus; to: CalendarEventStatus; changes?: AppointmentHistoryChange[]; } | { kind: 'edited'; /** Which fields changed, when the host knows no more than that. */ fields: AppointmentHistoryField[]; /** What each changed from and to, when the host recorded it. */ changes?: AppointmentHistoryChange[]; } | { kind: 'deleted'; } /** The patient confirmed by link; the host sends `actor: ''`. */ | { kind: 'confirmedByPatient'; }); export type AppointmentHistoryKind = AppointmentHistoryEntry['kind']; /** The create-document menu's kinds, as the platform's * AppointmentDocumentDropdownApp offers them. */ export type AppointmentDocumentKind = 'folder' | 'template' | 'empty' | 'identification' | 'webcam' | 'bill'; /** * One occurrence as the host reads it (platform * `getEventPopoverContentAction`, route * `ic_dcp_doctor_appointment_calendar_get_event_popover_content`). Pure * data, no callbacks. */ export interface AppointmentDetailsRead { /** The occurrence id the host was asked for, echoed back. An answer for * another id is never shown. */ eventId: string; appointment: { /** Local wall clock of THIS occurrence, 'YYYY-MM-DDTHH:mm:ss', no zone: * the pop-over route's `{date}` with a T. Not an instant: parse it with * `parseAppointmentStartIso`. */ startIso: string; /** Platform `DoctorAppointment.duration`, in minutes. */ durationMin: number; /** Platform `DoctorAppointment.allDay`. */ allDay: boolean; /** A video visit: the appointment has a `telehealthSessionId`. */ isTelehealth: boolean; /** Platform `DoctorAppointment.state`. Absent = no status recorded. */ status?: CalendarEventStatus; /** The free-text description: platform `DoctorAppointment.name`. */ name?: string; /** The host's heading for the appointment, as on the grid card. */ title: string; /** The appointment's colour as a hex. The platform stores a * `DoctorAppointment.colorId`; the host resolves it. */ colorHex?: string; /** Platform `DoctorAppointment.operatorId` (a DoctorOperator, not a * practice user). Informational: the visible label is the `operator` * prop. */ operatorId?: string; /** Platform `DoctorAppointment.chairId`. Informational: the visible * label is the `chair` prop. */ chairId?: string; /** The branch's display name, when the practice has branches. */ branchLabel?: string; /** Platform `DoctorAppointment::wasCreatedThroughBooking()`. */ bookedOnline: boolean; /** The series this occurrence belongs to. `requiresScope` is * informational: the scope question belongs to the host's editor. */ recurrence?: { frequency: AppointmentRecurrenceFrequency; count?: number; requiresScope: boolean; }; }; /** The booked patient, when the host could read them. Absent with a * `patientId` prop reads "Patient record not loaded". */ patient?: AppointmentDetailsPatient; /** Absent = not permitted. Each figure is gated on its own, in major * currency units. `billableAmount`: platform `GetBillableAmountService` * (needs accounting_show and doctor_care_plan_show). `unpaidAmount`: * platform `getUnpaidAmountByPatient` (needs accounting_show and * doctor_bill_show). Leave out a figure the user may not see, rather * than sending 0 as the pop-over does. */ money?: { billableAmount?: number; unpaidAmount?: number; }; /** The treatments on this appointment: platform * `DoctorAppointment::getDoctorCarePlanEntries()`. */ care?: { /** The linked plan (platform `DoctorAppointment.carePlanId`). Without * `number` the card shows the plan name alone. */ plan?: { id: string; number?: string; name: string; }; lines: Array<{ /** Platform `carePlanEntryId`. */ entryId: string; /** Platform `careId`. */ careId: string; /** Platform `Care.name`. */ name: string; /** Platform `Care.code`. */ code?: string; /** Platform `care_plan_entry_elements_as_string` (the teeth). */ elementsLabel?: string; /** Platform `CarePlanEntry.done`. */ done: boolean; /** At least one bill row for the entry (pop-over `dto.billed`). */ billed: boolean; /** Platform `scheduledCarePhases` for this appointment. */ phases: string[]; /** Absent = prices withheld for this viewer. */ priceCents?: number; }>; }; /** What the online booking recorded (platform * `DoctorAppointment.booking*`). */ booking?: { /** Platform `bookingFirstName` and `bookingLastName`. */ typedName?: string; /** Platform `bookingPhoneNumber`. */ phone?: string; email?: string; /** Platform `bookingNote`. */ note?: string; /** When the booking was made: an instant, with a zone. */ bookedAtIso?: string; /** Platform pop-over `onlinePaidAmount` (the StripePayment amount), in * major currency units. */ paidOnlineAmount?: number; }; /** Documents to sign for this patient. */ signing?: { /** Platform pop-over `numberOfRequiredObjects`. */ required: number; /** Platform pop-over `numberOfRequiredObjectsToSign`. */ toComplete: number; requestedVia?: AppointmentSignChannel; /** Platform pop-over `hasAdvancedElectronicSignatureSetup`. */ aesSetUp: boolean; }; /** The appointment's history, read with the details. Absent = not sent * with this read: the History button then follows the panel's `history` * prop, and without that too it is withheld. `[]` = nothing recorded * yet. */ history?: AppointmentHistoryEntry[]; } /** * The appointment's history when the host reads it on its own, once the * History sub-view opens (`onOpenHistory`). Present = the user may read it, * so the header offers History whatever this read has reached. */ export interface AppointmentDetailsHistory { /** The occurrence these entries answer for. Set it, and an answer for an * earlier appointment shows as loading instead of under this one. */ eventId?: string; /** The entries once read, in any order. `[]` = nothing recorded yet. */ entries?: readonly AppointmentHistoryEntry[]; /** Absent: 'ready' when `entries` is set, 'loading' when it is not. */ status?: 'loading' | 'ready' | 'error'; /** The failure to show on 'error'. Default: the kit's load error. */ message?: string; /** Read again after 'error'. Without it, the error offers no Retry. */ onRetry?: () => void; } /** What the panel shows. `ready` carries the read; the other three render * the shell: a skeleton, an error with Retry, or a refusal. */ export type AppointmentDetailsState = { status: 'loading'; eventId?: string; } /** `refreshing`: a re-read of the SAME occurrence is in flight. The body * stays on screen and is marked busy. */ | { status: 'ready'; read: T; refreshing?: boolean; } /** A transient failure. `message` defaults to the kit's load error. * `eventId`: the occurrence whose read failed, when known. */ | { status: 'error'; eventId?: string; message?: string; } /** Not visible to this user. Retry only when `retryable`. `eventId`: the * occurrence that was refused, when known. */ | { status: 'refused'; eventId?: string; reason: string; retryable?: boolean; }; /** One answer from the host's loader. */ export type AppointmentDetailsSnapshot = { status: 'ready'; details: T; } | { status: 'refused'; reason: string; retryable?: boolean; }; /** Reads one occurrence. A rejected promise is a transient failure (error * with Retry); the rejection's message is shown when it has one. */ export type AppointmentDetailsLoader = (eventId: string, options: { signal: AbortSignal; }) => Promise>; export interface UseAppointmentDetailsReadOptions { /** The occurrence to read. A new id aborts the previous read and shows * the skeleton. `null` = no selection: nothing is read, the read in * flight is aborted, and `state` is null, so render no panel. */ eventId: string | null; /** Reads one occurrence. The latest function is always the one called, * so an inline loader is fine: a new identity alone never reads again. * To read again, change `reloadKey`. */ load: AppointmentDetailsLoader; /** Change it after a write made anywhere in the rail, or when the loader * now reads from somewhere else: the same occurrence is read again * while the current read stays on screen. */ reloadKey?: string | number; } /** The operator's label, resolved by the host from * `appointment.operatorId`. */ export interface AppointmentDetailsOperatorLabel { name: string; avatarSrc?: string; } /** The chair's label, resolved by the host from `appointment.chairId`. */ export interface AppointmentDetailsChairLabel { name: string; } /** The patient's latest notes as the host holds them. The panel reads; it * never writes a note. They must be the notes of this appointment's * patient only: pass `patientId` so the panel can check. */ export interface AppointmentDetailsNotesPreview { /** The patient these notes belong to. Set it, and notes for another * patient show as loading, never under this appointment. */ patientId?: string; /** Newest first. The panel shows the first three, pinned first. */ notes: readonly NotesPanelNote[]; /** Absent = 'ready'. */ status?: 'loading' | 'ready' | 'error'; /** The full count, for "See more (n more)". Absent or null = unknown, and * the list length is used once ready. */ total?: number | null; /** Retry after status 'error'. Without it, the error has no Retry. */ onRetry?: () => void; } //# sourceMappingURL=appointment-details-panel.types.d.ts.map