99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
import { Button } from '@/components/ui/shared/Button';
|
|
import { PatientSearchCombobox } from '@/components/ui/patient/PatientSearchCombobox';
|
|
import { usePatientSearchQuery } from '@/lib/hooks/usePatientSearchQuery';
|
|
import { formatMobileForDisplay } from '@/lib/phone';
|
|
import type { Patient } from '@/types/patient';
|
|
|
|
const pickerChoiceClass =
|
|
'w-full rounded-[var(--radius-md)] border border-primary/40 bg-primary-soft px-3 py-2 text-start transition-colors hover:border-primary disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/45';
|
|
|
|
interface NewTreatmentPatientPickerProps {
|
|
creating?: boolean;
|
|
currentPatient?: {
|
|
id: string;
|
|
displayName: string;
|
|
mobile?: string | null;
|
|
email?: string | null;
|
|
} | null;
|
|
onSelectWalkIn: () => void | Promise<void>;
|
|
onSelectPatient: (patient: Patient) => void | Promise<void>;
|
|
onSelectCurrentPatient?: () => void | Promise<void>;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
function contactLine(
|
|
patient: { mobile?: string | null; email?: string | null },
|
|
fallback: string,
|
|
): string {
|
|
const mobile = patient.mobile?.trim()
|
|
? formatMobileForDisplay(patient.mobile.trim())
|
|
: '';
|
|
if (mobile) return mobile;
|
|
const email = patient.email?.trim();
|
|
if (email) return email;
|
|
return fallback;
|
|
}
|
|
|
|
export function NewTreatmentPatientPicker({
|
|
creating = false,
|
|
currentPatient = null,
|
|
onSelectWalkIn,
|
|
onSelectPatient,
|
|
onSelectCurrentPatient,
|
|
onCancel,
|
|
}: NewTreatmentPatientPickerProps) {
|
|
const t = useTranslations('treatment');
|
|
const tCommon = useTranslations('common');
|
|
const tPatients = useTranslations('patients');
|
|
const { search, setSearch, patients, loading } = usePatientSearchQuery(!creating);
|
|
|
|
return (
|
|
<div className="space-y-3 rounded-[var(--radius-md)] border border-border/70 bg-background-secondary/40 p-3">
|
|
<p className="text-sm font-medium text-text-primary">{t('newTreatmentPatientPrompt')}</p>
|
|
<button
|
|
type="button"
|
|
disabled={creating}
|
|
onClick={() => void onSelectWalkIn()}
|
|
className={pickerChoiceClass}
|
|
>
|
|
<p className="text-sm font-medium text-text-primary">{t('walkIn')}</p>
|
|
<p className="text-xs text-text-muted mt-0.5">{t('walkInPickerHint')}</p>
|
|
</button>
|
|
{currentPatient && onSelectCurrentPatient ? (
|
|
<button
|
|
type="button"
|
|
disabled={creating}
|
|
onClick={() => void onSelectCurrentPatient()}
|
|
aria-label={t('newTreatmentUseCurrent', { name: currentPatient.displayName })}
|
|
className={pickerChoiceClass}
|
|
>
|
|
<p className="text-sm font-medium text-text-primary break-words">{currentPatient.displayName}</p>
|
|
<p className="text-xs text-text-muted mt-0.5 break-words">
|
|
{contactLine(currentPatient, t('newTreatmentUseCurrentHint'))}
|
|
</p>
|
|
</button>
|
|
) : null}
|
|
<PatientSearchCombobox
|
|
search={search}
|
|
onSearchChange={setSearch}
|
|
patients={patients}
|
|
loading={loading || creating}
|
|
onSelectPatient={(patient) => {
|
|
if (creating) return;
|
|
void onSelectPatient(patient);
|
|
}}
|
|
placeholder={tPatients('searchPlaceholder')}
|
|
emptyResultsMessage={tPatients('noResults')}
|
|
/>
|
|
<div className="flex justify-end">
|
|
<Button type="button" variant="ghost" size="sm" disabled={creating} onClick={onCancel}>
|
|
{tCommon('cancel')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|