/** * TaskEditor: the published form for one task (platform `DoctorReminder`). * It creates, edits, completes and deletes a task through host seams, and * takes every list, label and seam as a prop, so a host builds it without * any calendar context. The calendar's own task route renders it through a * thin adapter. * * A note is the only field every host needs, beside ONE optional * counterpart: a patient, a company or an operator, never two, because the * platform refuses a task with "More than one counterpart is set". A host * that supplies `taskTypes` also gets a required Type select, and one that * supplies `assignees` an optional "Assigned to" (a practice user, not the * operator). A missing list or seam WITHHOLDS the control it backs; it never * renders that control disabled. * * Two layouts: `variant="panel"` fills a rail tool panel with its own header, * scroll body and pinned footer; `variant="subview"` IS a PanelSubview, for a * form stacked over a list such as TasksPanel. * * DOM hooks: `data-component="task-editor"` with `data-variant`, * `data-status` and `data-component-id`; the footer actions carry * `data-action` "cancel-task", "save-task" and "delete-task", and a failed * load's Retry carries "retry-load". In the subview the marker sits on the * body, so the PanelSubview section repeats `data-component-id`, * `data-variant` and `data-status`: `[data-component-id="…"]` reaches the * heading and the footer in both variants. */ import { type HTMLAttributes } from 'react'; import type { PatientIdentity } from '../_shared/patient-identity'; /** * A task as the editor loads and saves it: platform `DoctorReminder`. At * most ONE counterpart (patient, company or operator); the platform refuses * a task naming two. `note` is required here though the platform allows an * empty one. There is no title, priority or duration: the platform has none. */ export interface TaskEditorTask { id: string; /** An instant as ISO 8601 (platform `date`). The editor emits * `toISOString()`, so UTC with a `Z`. Parse it with `new Date()`, never by * slicing; a zone-less value reads as local time. */ dateIso: string; /** The task text (platform `note`). Required here, though the platform * allows an empty one. Never exposed through the agent. */ note: string; /** The clinician the task concerns: platform `doctorOperator`, an id from * `operators`. NOT the assignee. Exclusive with `patientId` and * `companyId`. */ operatorId?: string; /** The patient the task concerns: platform `doctorPatient` (column * `patientId`). Exclusive with `companyId` and `operatorId`. */ patientId?: string; /** Platform `doctorCompany`, an id from `companies`. Exclusive with * `patientId` and `operatorId`. */ companyId?: string; /** An opaque token from `taskTypes`, round-tripped untouched. Without * `taskTypes` the Type field is withheld and a new task sends none; a * stored one is still re-sent. */ typeId?: string; /** The practice USER the task is assigned to: platform `practiceUser`, an * id from `assignees`. Independent of the counterpart. Absent or '': * nobody. */ assigneeId?: string; /** Platform `done`. */ done: boolean; } /** What `onCreateTask` receives and what an edit save re-sends IN FULL, * never a patch. `done` carries the last value the host CONFIRMED, never an * optimistic or rolled-back toggle. */ export type TaskEditorDraft = Omit & { done?: boolean; }; /** One row of a host list. `label` is the host's own string, already * translated. `id` is opaque, unique in its list, never '' and never * 'none' (reserved for "Nobody"). */ export interface TaskEditorOption { id: string; label: string; } /** A task type. `custom` (practice-defined) only groups the Type select, * and only when the list mixes custom and standard types. */ export interface TaskEditorTaskType extends TaskEditorOption { custom?: boolean; } /** A patient as the picker needs one: an id and a display name. * `CalendarPatient` satisfies it. */ export type TaskEditorPatient = PatientIdentity; /** Host patient typeahead. Called 250 ms after typing stops, for non-blank * trimmed queries only. Abort a superseded call through `signal`. The * host's order is kept, so matches by phone or number stay. Reject on * failure (the picker offers Retry); `[]` is a successful search with no * match. The same contract as `CalendarData.searchPatients`. */ export type TaskEditorSearchPatients = (query: string, options: { signal: AbortSignal; }) => Promise; /** Where the form is. 'error': the load failed, with Retry. 'locked': edit * mode without `taskId` or `loadTask`, never a blank savable form. * 'withheld': the privacy notice covers an existing task. */ export type TaskEditorStatus = 'loading' | 'ready' | 'error' | 'locked' | 'withheld'; /** The one counterpart choice ("Concerns"). */ export type TaskEditorAbout = 'nobody' | 'patient' | 'company' | 'operator'; /** The draft as the agent reads it: identifiers and structure, never the * note body. Counterpart ids are what Save would send. */ export interface TaskEditorDraftState { mode: 'create' | 'edit'; dateIso: string | null; about: TaskEditorAbout; patientId: string | null; companyId: string | null; operatorId: string | null; typeId: string | null; assigneeId: string | null; /** The last CONFIRMED done value. */ done: boolean; hasNote: boolean; noteLength: number; } /** Fields an agent may set. Ids must resolve in the host lists, among the * patients the host's search returned, or equal the stored one. At most * one of `patientId`, `companyId` and `operatorId` per call; each also * selects its `about` branch. `assigneeId: null` is Nobody. */ export interface TaskEditorFieldPatch { dateIso?: string; note?: string; typeId?: string; assigneeId?: string | null; about?: TaskEditorAbout; patientId?: string; companyId?: string; operatorId?: string; } /** Curated agent handle. Writes return the host's promise, so a refusal * reaches the agent as a rejection. Every write fails unless the status is * 'ready' and the form is not read-only. */ export interface TaskEditorHandle { getStatus: () => TaskEditorStatus; getDraft: () => TaskEditorDraftState; canSave: () => boolean; /** Throws on an unknown id, two counterparts or a form not ready. */ setFields: (patch: TaskEditorFieldPatch) => void; save: () => Promise; setDone: (done: boolean) => Promise; /** Skips the confirmation dialog: the agent runtime confirms instead. */ deleteTask: () => Promise; /** Throws while a save or delete is in flight, as Cancel waits. */ cancel: () => void; } export interface TaskEditorProps extends Omit, 'children' | 'id' | 'title'> { /** Stable instance id, surfaced as `data-component-id` for the agent * registry. Generated when absent. */ id?: string; mode: 'create' | 'edit'; /** Edit mode: the task `loadTask` resolves. Without it, or without * `loadTask`, edit mode is 'locked'. */ taskId?: string; /** Create mode: the local date and time to prefill. Read at mount, when * the mode or task changes and at each subview reopen. Default: now. */ initialDate?: Date; /** 'panel' (default): fills its box with its own header, scroll body and * pinned footer, for a rail tool panel. 'subview': the editor IS a * PanelSubview; render it as the LAST, DIRECT child of a * `PanelSubview.Host`. */ variant?: 'panel' | 'subview'; /** Subview only. Default true. Each change from false to true starts a * fresh session: the draft resets and edit mode reloads. Keep one editor * mounted and toggle this, so it slides in and returns focus. */ open?: boolean; /** Subview only: fires once the exit has played. */ onExited?: () => void; /** The required Type select, defaulting to the first entry. */ taskTypes?: ReadonlyArray; /** The optional "Assigned to" select: practice users, not operators. */ assignees?: ReadonlyArray; /** The Company counterpart: a searchable Combobox matching the label, * never the id. */ companies?: ReadonlyArray; /** The Operator counterpart: clinicians. */ operators?: ReadonlyArray; /** Filtered on the client when `searchPatients` is absent. With neither, * the Patient counterpart is withheld; a stored link still shows * read-only and is re-sent on Save. */ patients?: ReadonlyArray; /** Host typeahead. Wins over `patients` for listing. May change identity * between renders; the next query uses the latest. */ searchPatients?: TaskEditorSearchPatients; /** Display name of the task's STORED patient when nothing else resolves * it, for example from the TasksPanel row that opened the form. */ patientLabel?: string; /** Resolves any patient id to a name. Tried before `patients` and * `patientLabel`. */ resolvePatientLabel?: (patientId: string) => string | undefined; /** Display name of a STORED company that `companies` does not list. * Without it the read-only row names the id. */ companyLabel?: string; /** Display name of a STORED operator that `operators` does not list. * Without it the row names the id. Each of the three labels names only * its own stored link, so a host may pass a TasksPanel row's * `linkedLabel` to the one its `linkType` names. */ operatorLabel?: string; /** Subview only: the back arrow's name and tooltip. Already translated. * Default "Back to tasks". */ backLabel?: string; /** Resolves the record for edit mode, once per task and session. Aborted * through `signal` when the task changes, the subview closes or the * privacy notice covers the form; a superseded answer is ignored. */ loadTask?: (id: string, options: { signal: AbortSignal; }) => Promise; /** Must resolve WITH the saved record: the host assigns the id. */ onCreateTask?: (draft: TaskEditorDraft) => Promise; /** A full re-send. Resolves with nothing. */ onUpdateTask?: (id: string, draft: TaskEditorDraft) => Promise; /** Called only after the confirmation dialog. */ onDeleteTask?: (id: string) => Promise; /** The Done switch (edit mode), called on the click. A rejection rolls * the switch back. */ onSetTaskDone?: (id: string, done: boolean) => Promise; /** Cancel, plus Back and Escape in the subview. Close the editor. None of * them fires while a save or delete is in flight. */ onCancel: () => void; /** After a create or update LANDS. The form then stops writing until it * is closed, remounted or reopened. The form cannot be cancelled while a * write is in flight, so this fires for every save the user started, * unless the host closes or unmounts the editor itself first. */ onSaved?: (task: TaskEditorTask) => void; /** After a delete LANDS. */ onDeleted?: (id: string) => void; /** Withholds every write control and disables the fieldset. */ readOnly?: boolean; /** Privacy mode: the linked patient shows as the mask, never the name. */ privacy?: boolean; /** How an EXISTING task opens under `privacy`. 'notice' (default): the * privacy notice covers the form, the record is not loaded, and the * footer holds Cancel only. 'mask': the form opens with the patient * masked and the note visible. Create mode always opens masked. */ privacyMode?: 'notice' | 'mask'; /** The notice's "Turn off privacy mode" action. Absent: no button. */ onPrivacyOff?: () => void; } export declare const TaskEditor: import("react").ForwardRefExoticComponent>; //# sourceMappingURL=task-editor.d.ts.map