import { type HTMLAttributes, type ReactNode } from 'react';
import { type VariantProps } from 'class-variance-authority';
import { type TaskItem } from '../task-card';
/** Which set of tasks the panel shows — the host owns the actual feed. */
export type TasksPanelScope = 'patient' | 'mine';
/** Status segment value. Mirrors the platform `done` filter (default `todo`). */
export type TasksPanelStatusFilter = 'todo' | 'done' | 'all';
/** Who filters the list: the panel ('client') or the host ('server'). */
export type TasksPanelFiltering = 'client' | 'server';
/** The whole filter state, as `onFiltersChange` reports it. */
export interface TasksPanelFilters {
status: TasksPanelStatusFilter;
type: string | null;
assignee: string | null;
overdueOnly: boolean;
}
/**
* Panel-local extension of the card's `TaskItem`. The extra fields here drive
* bucketing + filtering; the row's display fields (the typed link via
* `linkType` + `linkedLabel`, the `assigneeLabel`, `dueLabel`) live on
* `TaskItem` and are rendered by `TaskCard` itself. The host maps the platform
* `DoctorReminder` → this shape — composing `title` (a summary, NOT the bare
* type), `note`, the linked entity, the assignee, and `dueLabel`.
*/
export interface TasksPanelItem extends TaskItem {
/** ISO-8601 due/scheduled instant (platform `DoctorReminder.date`). Buckets + orders the list. */
dueIso: string;
/** System or custom task-type key (`recall` | `payment` | `collection` | `other` | `custom-*`). Drives the type filter + badge. */
type?: string;
/**
* Localised type label, required for CUSTOM types (the panel resolves system
* keys itself). Falls back to the system label for the four known keys,
* then to "Other". The raw key is never shown.
*/
typeLabel?: string;
/**
* Assignee id (the platform `practiceUser`). Omit ⇒ unassigned; give pool
* tasks a shared sentinel value (e.g. `'unassigned'`) if you want the pool
* itself filterable. Drives the assignee FILTER (exact match); the displayed
* name is `TaskItem.assigneeLabel`.
*/
assignee?: string;
/** Provenance — manual vs workflow-created. Filter only. */
createdBy?: 'user' | 'workflow';
}
/** Curated imperative handle — identifiers + structural state only, no PHI. */
export interface TasksPanelHandle {
getItems: () => Array<{
id: string;
status: string;
}>;
selectItem: (id: string) => void;
/**
* Same path as a click on the done box. Resolves once the host has
* answered (in client mode, after the row's fade) and rejects if the
* host refuses, or if nothing was started: an unknown id, a read-only
* panel, no `onToggleDone`, or a change to that task already pending.
*/
toggleDone: (id: string) => Promise;
/** Same path as the row's dismiss action, and settles in the same way. */
dismiss: (id: string) => Promise;
jumpToOverdue: () => void;
setFilter: (next: {
status?: TasksPanelStatusFilter;
type?: string | null;
assignee?: string | null;
overdueOnly?: boolean;
}) => void;
/** Switch the task scope segment (patient ↔ mine). The host re-feeds items. */
changeScope: (next: TasksPanelScope) => void;
/** Request the host to append the next page of tasks (mirrors onLoadMore). */
loadMore: () => void;
}
interface FilterOption {
/** Stable value passed back through the matching `on…Change`. */
value: string;
/** Localised label. */
label: string;
}
export interface TasksPanelProps extends Omit, 'onClick' | 'id' | 'title'> {
/** Stable instance id, surfaced as `data-component-id`. */
id?: string;
/** Tasks to render — any order; bucketed + sorted internally (due ASC, Done last). */
items: TasksPanelItem[];
/** ISO-8601 "now" anchor — buckets + overdue compute against it. Defaults to the current instant; stories pass a fixed value. */
nowIso?: string;
/** Panel heading. Defaults to the localised "Tasks". */
title?: string;
/** PanelHeader glyph. Default . */
icon?: ReactNode;
/**
* A total the host counted, shown as given after the title ("Tasks (42)")
* and read as "42 tasks". Omitted: nothing shown. The panel never
* computes it.
*/
totalCount?: number;
/** Visual density forwarded to each TaskCard. Defaults to `sm`, matching the note and appointment rows. */
size?: 'xs' | 'sm' | 'md';
/**
* 'client' (default): the panel filters `items`. 'server': the host has
* already filtered and paged `items`; the panel shows them as given. In
* server mode a filter control shows only when `onFiltersChange` or its
* own `on…Change` is set, since only the host can re-query.
*/
filtering?: TasksPanelFiltering;
/** Fires when a row is activated. */
onOpenTask?: (item: TasksPanelItem) => void;
/**
* Fires when the host should set a task's done state to `nextDone`. In
* server mode it fires on the click. In client mode it fires on the click
* for a row that stays in view, and once the row has faded for one that
* leaves. Either way, the box ignores clicks until a returned promise
* settles, and a promise that rejects puts the row back, unticked. The
* panel does not announce a refusal: report it yourself (for example
* `toastError` with a translated message) and rethrow, so the row still
* rolls back.
*/
onToggleDone?: (item: TasksPanelItem, nextDone: boolean) => void | Promise;
/** Fires when the host should open the task editor. Omit to hide the per-row edit action. */
onEditTask?: (item: TasksPanelItem) => void;
/**
* Fires when the host should dismiss / remove a task. Omit to hide the
* per-row dismiss action. In server mode it fires on the click, in client
* mode once the row has faded. A returned promise that rejects restores
* the row. The panel does not announce a refusal: report it yourself and
* rethrow, as for `onToggleDone`.
*/
onDismissTask?: (item: TasksPanelItem) => void | Promise;
/** Fires when the header "Add" affordance is activated. Omit to hide it. */
onAddTask?: () => void;
/**
* Read-only mode — hides every write affordance (toggle / edit / dismiss /
* add), and the handle's `toggleDone` and `dismiss` reject without calling
* the host. Mirrors the platform gating writes behind `practice_task_edit`.
*/
readOnly?: boolean;
/**
* Per-row trailing action toolbar, forwarded to each TaskCard's `actions`
* slot. Overrides the default toggle / edit / dismiss buttons (the type Badge
* is always prepended by the panel). Ignored when `readOnly`.
*/
renderItemActions?: (item: TasksPanelItem) => ReactNode;
/**
* Extra per-row actions, rendered before the toggle / edit / dismiss
* actions (or before `renderItemActions`, which still replaces those).
* Not a task write, so `readOnly` does not hide them: withhold them from
* the host side when they do not apply.
*/
renderItemExtraActions?: (item: TasksPanelItem) => ReactNode;
/** Controlled scope segment. */
scope?: TasksPanelScope;
/** Uncontrolled initial scope. Default `'patient'` when both scopes are available, else the only one. */
defaultScope?: TasksPanelScope;
/** Fires when the user switches scope — the host re-feeds `items`. */
onScopeChange?: (next: TasksPanelScope) => void;
/** Which scopes the segment offers. Omit ⇒ no scope segment (single-scope panel). */
availableScopes?: TasksPanelScope[];
/** Load the next page — fired once per page when the bottom edge is reached, and via the footer button. */
onLoadMore?: () => void;
/** Whether more tasks exist. Defaults to `true` when `onLoadMore` is set (client mode only; server mode needs `true`). */
hasMore?: boolean;
/** Show the bottom loading row while the next page is fetched. */
loadingMore?: boolean;
/** Loading state. Skeleton rows while there are none to show; in server mode, rows already shown stay. */
loading?: boolean;
/** Error surface — `true` for the default error EmptyState, or a custom node. */
error?: boolean | ReactNode;
/** Adds a Retry action to the default error state (`error === true`). */
onRetry?: () => void;
/**
* Show the filter header (status segment + type / assignee selects +
* overdue toggle). Default `true`. In server mode each control also needs
* `onFiltersChange` or its own `on…Change`.
*/
showFilters?: boolean;
/**
* The host's default query: the uncontrolled start, what Clear filters
* returns to, and what server mode judges "filtered empty" against.
* Absent: To do, any type, anyone, not overdue-only.
*/
defaultFilters?: TasksPanelFilters;
/**
* Once per user action, with the whole next filter state. Never fires
* when nothing changed. Fires in both modes.
*/
onFiltersChange?: (next: TasksPanelFilters) => void;
/** Controlled status segment. */
statusFilter?: TasksPanelStatusFilter;
/** Uncontrolled initial status segment. Default `'todo'` (the platform default). */
defaultStatusFilter?: TasksPanelStatusFilter;
onStatusFilterChange?: (next: TasksPanelStatusFilter) => void;
/** Task types for the type filter. Omit ⇒ no type filter. */
typeOptions?: FilterOption[];
/** Controlled type filter (`null`/empty ⇒ all). */
typeFilter?: string | null;
onTypeFilterChange?: (next: string | null) => void;
/** Assignees for the assignee filter. Pass an explicit Unassigned option if wanted. Omit ⇒ no assignee filter. */
assigneeOptions?: FilterOption[];
/** Controlled assignee filter (`null`/empty ⇒ anyone). */
assigneeFilter?: string | null;
onAssigneeFilterChange?: (next: string | null) => void;
/** Controlled overdue-only toggle. */
overdueOnly?: boolean;
/** Uncontrolled initial overdue-only state. Default `false`. */
defaultOverdueOnly?: boolean;
onOverdueOnlyChange?: (next: boolean) => void;
emptyTitle?: string;
emptyDescription?: string;
/** CTA wired into the first-use empty state (e.g. Create task). */
emptyAction?: {
label: string;
onClick: () => void;
};
/** Accessible name for the panel region (defaults to `title`). */
'aria-label'?: string;
}
declare const rootVariants: (props?: ({
size?: "sm" | "md" | "xs" | null | undefined;
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
export declare const TasksPanel: import("react").ForwardRefExoticComponent>;
/** Re-export of the size variant union for downstream typing. */
export type TasksPanelSize = NonNullable['size']>;
export {};
//# sourceMappingURL=tasks-panel.d.ts.map