/** * Calendar pattern — the WORKSPACE ROOT. * * The practice agenda page the platform mounts from the kit: the grid * and its two-strip toolbar (`calendar.surface.tsx`) beside a MINIMAL * Control Room rail docked inline-end — up to six tools, in strip * order: the upcoming Appointments panel (the rail's resting state and * its default), ONE appointment editor / details tool, the waiting list, * patient notes when in context, online bookings and the free-slot * finder. The kit adds nothing else: no Inbox, no Alia, no patient tabs, * no care plan, no tasks. A host may append its own tools after these * (`extraTools`), which is how a host mounts TasksPanel. The panels are * the extracted modules from `calendar.sheets.tsx`; `calendar.host.ts`'s * open / close seam maps onto the rail's dock, with `close()` returning * to the Appointments panel so no path leaves an empty rail. * * Six is the ceiling, not the floor: `availableTools` narrows the set * to what the host can actually back with data, hiding the rail tool AND * the toolbar control that opens it, and routing anything aimed at a * withheld tool to the Appointments panel. A host with no business * hours, holidays or closures must withhold the free-slot finder — it * would otherwise report booked and shut-day times as free. * * The host owns the data and the domain state: it hands in a * `CalendarData` read model (`calendar.data.ts`) and a `CalendarJourney` * (`calendar.journey.ts`) — the demos from their fixtures and the * fixture-backed journey hook, the platform island from * `/internalApi/...`. The workspace owns what sits between the two: the * cog's display settings, the privacy flag, the one polite region, the * editor tool's mode, the placement / create modes, the deep-link * effects and the tasks filter. It renders NO header or sidebar — the * host page has its own — and expects a bounded block-size from its * parent (it fills it; the surface scrolls inside, the rail stays put), * exactly what `AppFrame`'s `
` gives a page. * * The confirmations stay kit `Dialog`s — they interrupt rather than * dock: the move confirm, the delete confirm with its recurring scope * and care-plan cleanup and the Create-bill confirm. */ import type { CalendarView } from '../../components/calendar'; import { type ControlRoomFavoriteChange, type ControlRoomTool } from '../../components/control-room-rail'; import { type CalendarAppointmentEditorLoader } from './calendar-editor-hydration'; import { type CalendarAppointmentDetailsLoader } from './calendar-appointment-details'; import { type CalendarData } from './calendar.data'; import type { CalendarJourney } from './calendar.journey'; import { type CalendarPreferenceAdapter } from './calendar.preferences'; import { type CalendarRow, type EditorPrefill } from './calendar.model'; import { type CalendarWaitingListState, type CalendarWorkspaceToolId, type WorkspaceDisplaySettings, type BacklogGap } from './calendar-workspace.model'; export { WORKSPACE_TOOL, type CalendarWorkspaceToolId, } from './calendar-workspace.model'; /** Host-owned queue membership, independent of the visible calendar range. */ export interface CalendarOnlineBookings { rows: readonly CalendarRow[]; status: 'loading' | 'ready' | 'error'; /** Opt in only when the host can load and persist canonical editor data. */ canComplete?: boolean; /** Unknown or invalid means no booking receives a New marker. */ lastSeenIso?: string | null; /** Creation timestamps when the booking has no contact payload. */ createdAtById?: Readonly>; onRetry?: () => void; } /** Where a tool panel was opened from. `system` is a dock the workspace * made on its own (the resting panel, a return after a save, a * toolbar or journey hand-off), not a pick on the rail. */ export type CalendarToolOpenSource = 'rail' | 'appointment_panel' | 'selection' | 'system'; /** Where the appointment behind a details panel was selected. `host` is * a selection the journey made itself. */ export type CalendarDetailsOpenSource = 'calendar' | 'appointments_panel' | 'bookings_panel' | 'waiting_list' | 'deep_link' | 'host'; /** * A host tool on the workspace rail (`CalendarWorkspaceProps.extraTools`): * a Control Room tool, plus what privacy mode does to it. `scope` and * `reorderable` are ignored: the workspace rail orders by favourites. */ export interface CalendarWorkspaceExtraTool extends ControlRoomTool { /** * What privacy mode does to this tool's panel. The kit cannot see what * the panel shows, so the default is the safe one. * * - `'withhold'` (default): while privacy mode is on, the panel and its * `header` are replaced by the privacy notice, titled with `label` and * offering "Turn off privacy mode". The host's panel is unmounted until * privacy is turned off. The strip button and `badgeCount` stay. * - `'host'`: the panel stays mounted and the host masks it, reading the * mode from `onPrivacyChange` (a task list sets `linkedLabelMasked`; * mind free-text titles, which the grid hides under privacy). * Also the right value for a panel that shows no patient data at all. * The kit cannot check either claim. */ privacy?: 'withhold' | 'host'; /** * Keeps this tool docked while it is open and `hold` is true: a click on * the grid (a card, a card with no patient, an empty slot) then changes * the selection but docks nothing over the panel, neither the details nor * the editor. Set it while the panel holds unsaved input, such as an open * task editor. Explicit requests still open another tool: the strip, the * toolbar, a free band's Book, a slot picked in an armed placement, and * the host. Once released, a click on the card docks its details. */ hold?: boolean; } /** * Usage callbacks for the host's analytics. The kit raises no event of * its own and passes identifiers only, never patient data: the host owns * the calls, the property allow-list and any once-per guard. */ export interface CalendarWorkspaceAnalytics { /** A built-in tool panel opened. Not raised again while it stays open, * and never for an id no tool carries. Host tools report through * `onExtraToolOpen` instead. */ onToolOpen?: (event: { toolId: CalendarWorkspaceToolId; source: CalendarToolOpenSource; view: CalendarView; }) => void; /** An appointment's preview opened. Raised on EVERY opening: the * once-per-appointment guard is the host's. */ onAppointmentPreviewOpen?: (event: { eventId: string; view: CalendarView; privacy: boolean; }) => void; /** An appointment's details docked for a new selection. Not raised when * details come back after an edit. */ onAppointmentDetailsOpen?: (event: { eventId: string; source: CalendarDetailsOpenSource; view: CalendarView; }) => void; /** A rail preference was saved on this device. `toolId` may be a host * tool's id. `resize` is raised only with `persistPanelWidthKey`, since * without it nothing is saved. */ onToolPreferencesChange?: (event: { action: ControlRoomFavoriteChange['action'] | 'resize'; toolId: string; }) => void; /** * A host tool's panel opened (`extraTools`). Not raised again while it * stays open. `rail` is a pick on the strip; `system` is any other open * (the host's own `useControlRoomActions().openTool`, an agent). */ onExtraToolOpen?: (event: { toolId: string; source: Extract; view: CalendarView; }) => void; } export interface CalendarWorkspaceProps { /** Optional canonical read for every existing-appointment editor route. * The form waits for this response; failures/refusals expose no editable * grid-derived prefill. Keep the callback stable and cache patient data * before resolving. Required for host online-booking completion. */ loadAppointmentEditor?: CalendarAppointmentEditorLoader; /** The host's read for the read-only appointment details panel (P-C3). * With it a selection docks that panel, Edit opens the editor and a save * re-reads the details; without it details stays the editor form. Keep * it stable: passing a new function re-reads the open details panel, * which is how a host shows a change made outside the panel. */ loadAppointmentDetails?: CalendarAppointmentDetailsLoader; /** The domain state and its handlers — the host implements them. */ journey: CalendarJourney; /** The practice's read model, stable for a page load. */ data: CalendarData; /** Supplied rows are authoritative, including an explicitly empty queue. */ onlineBookings?: CalendarOnlineBookings; /** The host's read of the waiting list: a first `loading` shows * placeholders, `error` offers `onRetry`. Omitted = always ready. */ waitingListState?: CalendarWaitingListState; /** When each appointment entered its current state, by event id (ISO * 8601). The upcoming panel shows "waiting for 12 min" only for rows * listed here; it never derives an elapsed time from the start. */ stateSinceByEventId?: Readonly>; locale?: string; /** Host-level cog overrides. */ initialDisplay?: Partial; /** The platform's `practice_appointment_edit` right — false turns the * page read-only (no drag, no create, details without write actions). */ permissions?: { editAppointment?: boolean; }; /** Deep link `focusAppointmentId`: open on the appointment's day, ring * and scroll to its card and dock its details. */ focusAppointmentId?: string; /** Addresses one online booking: docks the bookings tool, then scrolls * to, focuses and rings its card. An id outside the queue rings nothing. */ focusBookingId?: string; /** Bump to address the SAME booking again (a second notification): an * address is a request, not a value. */ focusBookingKey?: string | number; /** Deep link `createParameters`: arrive in CREATE mode — the banner * stands and the next free-slot click opens the editor prefilled. */ createParameters?: { prefill: EditorPrefill; patientName?: string; }; /** Deep link `freeSlotClicked`: jump to the slot and open the editor * prefilled with it. */ freeSlot?: EditorPrefill; /** Platform advisories the finder raises when the scheduler is empty. */ businessHours?: { practice?: boolean; operators?: boolean; }; /** * The tools this host can back with real data. Omitting one hides both * its rail tool and the toolbar control that opens it. Defaults to all * five. * * A tool a host cannot supply must not be offered: the free-slot * finder without the practice's business hours, holidays and closures * reports slots as free that are already booked or fall on days the * practice is shut, and staff ring patients with them. `appointments` * is always included — the rail docks it as its resting panel — and a * route into a tool left out (a deep link, a panel hand-off) lands * there instead of on an empty dock. */ availableTools?: readonly CalendarWorkspaceToolId[]; /** * The practice's FullCalendar Scheduler licence key, passed straight * through to the kit `Calendar`. * * Chairs and operators as columns are Scheduler premium views, so a * host that does not supply a key renders the "Your license key is * invalid" watermark across the resource columns. The kit ships no key * and invents no default: omit this and the grid behaves exactly as it * did before the prop existed, falling through to whatever the * `Calendar` component's own environment default resolves to. */ schedulerLicenseKey?: string; /** In an appointment's History, say "What changed was not recorded." * under an edit that carries no detail. Off by default. Turn it on * once the host records what each save changed, so the older entries * read as incomplete rather than empty. */ noteUnrecordedEdits?: boolean; /** Where the cog's settings, the view, the weekend choice and the * chair / operator selections are remembered — the host's per-user * store. Defaults to the session-scoped in-memory adapter. */ preferences?: CalendarPreferenceAdapter; /** The grid's instance id — the agent registry's handle for this * calendar and the DOM hook anything outside React reaches it by. * Override it only when two workspaces share one page. */ id?: string; /** * The window the free-slot finder is about to search, raised before it runs. * * The finder projects free time from `journey.rows` and nothing else, so a * host whose rows cover only the visible grid would have it report booked * time as FREE over a range the user chose and the host never heard about. * Load that range on this call: new rows re-render the panel, the search * runs again, and the second answer is the correct one. * * A host that offers the finder without honouring this is showing staff free * slots it has not checked. */ onFinderSearchRange?: (range: { from: Date; to: Date; }) => void; /** The backlog panel's three switches ("Senza paziente" / "Senza * operatore" / "Senza poltrona"), all on by default and reached * through the panel header's settings cog. Controlled together with * `onBacklogFiltersChange`; omit both and the panel keeps its own * uncontrolled state — the kit does not persist this itself. The chair * switch is hidden when the practice has no chairs, and a chair value * it cannot show never marks the cog. */ backlogFilters?: Readonly>; onBacklogFiltersChange?: (filters: Readonly>) => void; /** Device key the rail saves pinned tools and their order under. Omit * it and the rail keeps its generic default, `ui.controlRoom.favorites`. */ persistFavoritesKey?: string; /** Device key the rail saves the panel width under. Omit it and width * is not saved, as before. On-device only; nothing reaches a server. */ persistPanelWidthKey?: string; /** Usage callbacks for the host's analytics. Omit it and nothing is * reported. */ analytics?: CalendarWorkspaceAnalytics; /** * Host tools, appended to the rail after the built-in ones, in this * order. A tool is offered by passing it and withheld by leaving it out: * `availableTools` narrows the built-in tools only. * * Ids: every `WORKSPACE_TOOL` id is reserved, even for a tool * `availableTools` withholds, and a tool using one is left off the rail. * A repeated id keeps its first tool. Nothing throws. The kit's own * Storybook and tests warn; the published build drops the tool silently. * * The panel renders inside the workspace, so the host's own providers * reach it. It unmounts when another tool opens, so keep state that must * survive in the host. Render a kit `PanelHeader` first and the rail puts * its favourite and close controls in it. Opening one reports through * `analytics.onExtraToolOpen`. The workspace passes no permission to host * tools. Only the strip, an agent and code inside an open panel * (`useControlRoomActions`) open a tool: code outside the workspace * cannot, since the workspace owns its rail provider. If the host stops * passing the open tool, the rail docks the Appointments panel. Memoise * the array: a new one re-registers the rail. */ extraTools?: readonly CalendarWorkspaceExtraTool[]; /** * Raised when privacy mode turns on or off, from the toolbar toggle or a * withheld panel's "Turn off privacy mode". Observe-only: the workspace * owns the flag, it starts off on every mount (not reported) and is * never persisted. Unmounting with privacy on reports `false`, so the * host's copy never outlives the workspace. Raised in the same handler, * so a host that sets state here renders its masked tool in the same * commit as the masked grid. */ onPrivacyChange?: (privacy: boolean) => void; } /** * The cog's settings as the workspace opens with them: the remembered * entry under the workspace's own key, a caller's `initialDisplay` * winning for the keys it names. Exported so a host seeding its journey * (row colours follow `colorAssociation`) starts from the same values * the workspace will show. */ export declare function initialWorkspaceDisplay(initialDisplay: Partial | undefined, preferences?: CalendarPreferenceAdapter): WorkspaceDisplaySettings; /** * The workspace root: the tooltip scope, the read model, the rail scope, * then the page. * * `TooltipProvider` is the workspace's own, not the host's. The toolbar * and the panels are built from `IconButton`s carrying `tooltip`, and * Radix throws `Tooltip must be used within TooltipProvider` the moment * one renders with no provider in scope — which unmounts the whole React * tree, so a host that simply mounts `` gets a BLANK * PAGE rather than a missing tooltip. A page-level pattern cannot leave * that to a host that has no way of knowing which Radix primitive it * happens to compose. Radix's provider is a plain scoped context: an * inner one shadows the outer value for its own subtree and registers * nothing globally, so a host that already has one (an app shell, the * Storybook decorator, this pattern's tests) is unaffected. * The delays are the kit defaults: shadowing means the workspace does not * inherit a host's tuning, which is the right trade for a page that has to * render at all. */ export declare function CalendarWorkspace({ data, ...props }: CalendarWorkspaceProps): import("react/jsx-runtime").JSX.Element; //# sourceMappingURL=calendar-workspace.d.ts.map