Vyhledání pacienta
Najde pacienta podle jména, rodného čísla nebo telefonu a nabídne cestu ven, když nalezen není.
identityinput
Install
pnpm dlx shadcn@4.23.0 add @ansora/patient-searchExamples
Usage
Fully controlled patient lookup: a field, a native radio group of candidates, and the "nenalezen -> zalozit" exit. It fetches nothing, debounces nothing, caches nothing and filters nothing — results are whatever your screen hands it, in your order.
Required: value (PatientCandidate | null) + onChange, query + onQueryChange, results, status ("idle" | "loading" | "error"), caption (the VISIBLE field label), resultsLabel (the INVISIBLE name of the group), loadingLabel, and empty / error, each one object of { title, description } so half an empty state cannot be supplied. Optional: create: { label, onCreate }, clear: { label }, className. No rest spread — className is the only escape hatch.
value and query are independent, and that is the point. The receptionist's live-call screen arrives with value already set to the AI's guess from the calling number while query is still empty, because nobody typed anything; reservations' Nova rezervace starts with both empty and is typed at. A combobox that projects its selection into its own text field would show a name nobody searched for on the first screen, and clearing the box would clear the patient. Never write one prop from the other.
The confirmed patient is always among the options, even when a newer results no longer contains it — it is prepended. So a background re-query, a failed one included, can never make the chosen patient disappear.
Never in the URL. The search query is the one filter that must NOT become a query parameter, and that is a deliberate, documented exception to the web-ui-conventions default. A rodne cislo in browser history, in a server access log or in a pasted link is a data incident. Hold query in component state on the screen; put the CHOSEN patient's id in the URL if you need one there.
Validate the query where you issue it, not here. A query that IS a birth number should be looked up exactly rather than fuzzily — call isValidRodneCislo from @ansora/core/schema in your data layer and switch the lookup, then say so in the row's flags. It is deliberately NOT imported by this component: the component does not choose which patients appear, so the check would decide nothing here, and that module builds a zod schema at module scope — importing it from a "use client" file put 947 092 B raw / 263 136 B gzip of zod into one client chunk the last time it happened (packages/core/package.test.ts).
A failed search never offers creation. create renders on the empty branch and beneath a populated list, never on error: a search that fell over is not evidence that the patient does not exist, and a create button there is how a duplicate record is made for somebody already in the system. clear is never withheld — un-choosing a wrong guess invents nothing.
Rows are native <input type="radio"> in a <fieldset>: arrow keys move and select, so "confirm or override in one keystroke" comes from the platform rather than from a keydown handler nothing in this repo can execute. The mark is accent-foreground, never bg-primary — green is the brand colour, not a state. An identifier renders through .identifier (slashed zero) and never truncates; a birth date renders through .num and is formatted here by formatDate; the NAME is what gives way.
import { PatientSearch, type PatientCandidate } from "@/components/ansora/patient-search"
import { StatusPill } from "@/components/ui/ansora/status-pill"
const [query, setQuery] = React.useState("")
const [patient, setPatient] = React.useState<PatientCandidate | null>(null)
<PatientSearch
caption="Pacient"
resultsLabel="Nalezeni pacienti"
loadingLabel="Hledam…"
query={query}
onQueryChange={setQuery}
value={patient}
onChange={setPatient}
status={state.status}
results={state.results.map((p) => ({
id: p.id,
name: p.name,
identifier: { caption: "Rodne cislo", value: p.rodneCislo },
born: { caption: "Narozena", at: p.bornAt },
contact: p.phone,
flags: p.exact ? <StatusPill tone="info">Shoda podle rodneho cisla</StatusPill> : undefined,
}))}
empty={{
title: "Nikdo takovy v kartotece neni",
description: "Zkuste rodne cislo nebo telefon. Pokud pacient chybi, zalozte ho.",
}}
error={{
title: "Vyhledavani se nezdarilo",
description: "Spojeni s kartotekou vypadlo. Zkuste to prosim za okamzik znovu.",
}}
create={{ label: "Zalozit noveho pacienta", onCreate: openCreateDialog }}
clear={{ label: "Neni to on ani ona" }}
/>
Files
registry/ansora/components/patient-search.tsx→@components/ansora/patient-search.tsx
Dependencies
@ansora/base@ansora/empty-statehttps://ui.shadcn.com/r/styles/base-nova/input.jsonhttps://ui.shadcn.com/r/styles/base-nova/button.json
Patient header
Identity band at the top of a record: name, one identifier typeset with a slashed zero, and a named key-facts block, with slots for status flags and a correcting action.
Session bar
The strip that says whether capture is running: a toned state pill, an optional degradation notice, the elapsed duration and the controls that change it.