95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import { Search } from 'lucide-react';
|
|
import { useTranslations } from 'next-intl';
|
|
import { Button } from '@/components/ui/shared/Button';
|
|
import { Input } from '@/components/ui/shared/Input';
|
|
import { formatMobileForDisplay } from '@/lib/phone';
|
|
import type { Patient } from '@/types/patient';
|
|
|
|
interface AppointmentsPatientSearchProps {
|
|
search: string;
|
|
onSearchChange: (value: string) => void;
|
|
patients: Patient[];
|
|
selectedPatientId?: string;
|
|
onSelectPatient: (patient: Patient) => void;
|
|
loading?: boolean;
|
|
canAddPatient: boolean;
|
|
onAddPatient: () => void;
|
|
}
|
|
|
|
export function AppointmentsPatientSearch({
|
|
search,
|
|
onSearchChange,
|
|
patients,
|
|
selectedPatientId,
|
|
onSelectPatient,
|
|
loading = false,
|
|
canAddPatient,
|
|
onAddPatient,
|
|
}: AppointmentsPatientSearchProps) {
|
|
const t = useTranslations('appointments');
|
|
const tPatients = useTranslations('patients');
|
|
|
|
const trimmed = search.trim();
|
|
const showAddForEmptyResults =
|
|
trimmed.length > 0 && !loading && patients.length === 0;
|
|
|
|
return (
|
|
<div className="surface-card p-4 space-y-4">
|
|
<div className="flex flex-col sm:flex-row gap-3 sm:items-center">
|
|
<div className="flex-1">
|
|
<Input
|
|
placeholder={t('searchPlaceholder')}
|
|
value={search}
|
|
onChange={(e) => onSearchChange(e.target.value)}
|
|
icon={<Search className="h-4 w-4 icon-flat" />}
|
|
/>
|
|
</div>
|
|
{showAddForEmptyResults && (
|
|
<Button
|
|
type="button"
|
|
variant="primary"
|
|
disabled={!canAddPatient}
|
|
onClick={onAddPatient}
|
|
title={!canAddPatient ? t('noPermissionAdd') : undefined}
|
|
>
|
|
{tPatients('newPatient')}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2 max-h-72 overflow-y-auto">
|
|
{loading && <p className="text-sm text-text-muted">{t('searching')}</p>}
|
|
|
|
{!loading && trimmed.length === 0 && (
|
|
<p className="text-sm text-text-muted">{t('searchHint')}</p>
|
|
)}
|
|
|
|
{patients.map((patient) => {
|
|
const isSelected = selectedPatientId === patient.id;
|
|
return (
|
|
<button
|
|
key={patient.id}
|
|
type="button"
|
|
onClick={() => onSelectPatient(patient)}
|
|
className={`w-full text-left rounded-[var(--radius-sm)] border px-3 py-2 transition-colors ${
|
|
isSelected
|
|
? 'bg-primary-soft border-primary/60'
|
|
: 'border-border/60 hover:bg-background-card/70'
|
|
}`}
|
|
>
|
|
<p className="text-sm font-medium text-text-primary">
|
|
{patient.firstName} {patient.lastName}
|
|
</p>
|
|
<p className="text-xs text-text-muted">
|
|
{formatMobileForDisplay(patient.mobile) || patient.email || tPatients('noContact')}
|
|
</p>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|