mirror of
https://github.com/temetro/temetro.git
synced 2026-08-27 19:06:52 +00:00
frontend: keyboard combobox for appointments + clickable edit sheet
- new reusable <Combobox> (Base UI Autocomplete): arrow-key + Enter navigation - New Appointment dialog: patient search and provider are now searchable comboboxes (provider sourced from /api/staff/providers) instead of a hand- rolled dropdown and a free-text input - appointment rows are clickable and open an <AppointmentDetailSheet> to edit date/time/type/provider/status or delete; "Added by AI" badge shown on rows and in the sheet for source="ai" records Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteInput,
|
||||
AutocompleteItem,
|
||||
AutocompleteList,
|
||||
AutocompletePopup,
|
||||
} from "@/components/ui/autocomplete";
|
||||
|
||||
export type ComboboxOption = {
|
||||
// Stable value handed back to onSelect.
|
||||
value: string;
|
||||
// Text used both for type-to-filter matching and the committed input text.
|
||||
// Fold any extra searchable terms (e.g. a file number) in here.
|
||||
label: string;
|
||||
// Optional rich row content for the dropdown; falls back to `label`.
|
||||
node?: ReactNode;
|
||||
};
|
||||
|
||||
// A searchable single-select built on Base UI's Autocomplete, so arrow-key
|
||||
// navigation and Enter-to-select work out of the box (same primitive as the ⌘K
|
||||
// command palette). Filtering matches the option `label`. `onSelect` fires for
|
||||
// both mouse clicks and keyboard Enter.
|
||||
export function Combobox({
|
||||
options,
|
||||
onSelect,
|
||||
placeholder,
|
||||
emptyText,
|
||||
autoFocus,
|
||||
value,
|
||||
defaultValue,
|
||||
onValueChange,
|
||||
inputClassName,
|
||||
}: {
|
||||
options: ComboboxOption[];
|
||||
onSelect: (value: string) => void;
|
||||
placeholder?: string;
|
||||
emptyText?: string;
|
||||
autoFocus?: boolean;
|
||||
// Optionally control (value) or seed (defaultValue) the input text.
|
||||
value?: string;
|
||||
defaultValue?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
inputClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<Autocomplete
|
||||
defaultValue={defaultValue}
|
||||
items={options}
|
||||
onValueChange={onValueChange}
|
||||
value={value}
|
||||
>
|
||||
<AutocompleteInput
|
||||
autoFocus={autoFocus}
|
||||
className={inputClassName}
|
||||
placeholder={placeholder}
|
||||
showTrigger
|
||||
/>
|
||||
<AutocompletePopup>
|
||||
{emptyText ? <AutocompleteEmpty>{emptyText}</AutocompleteEmpty> : null}
|
||||
<AutocompleteList>
|
||||
{(opt: ComboboxOption) => (
|
||||
<AutocompleteItem
|
||||
key={opt.value}
|
||||
onClick={() => onSelect(opt.value)}
|
||||
value={opt.label}
|
||||
>
|
||||
{opt.node ?? opt.label}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompletePopup>
|
||||
</Autocomplete>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user