Files
dyolink/frontend/src/components/ui/treatment/AppointmentsStrip.tsx

128 lines
4.5 KiB
TypeScript
Raw Normal View History

'use client';
import { useTranslations } from 'next-intl';
import { CalendarDays } from 'lucide-react';
import { Card } from '@/components/ui/shared/Card';
import { ScheduleDayPicker } from '@/components/ui/shared/ScheduleDayPicker';
import { startOfLocalDay } from '@/components/appointments/appointmentTime';
import { DayStripCard } from '@/components/ui/treatment/DayStripCard';
import type { DayStripItem } from '@/components/treatment/dayStrip';
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
interface AppointmentsStripProps {
stripHidden: boolean;
onToggleStripHidden: () => void;
selectedDay: Date;
onSelectDay: (day: Date) => void;
items: DayStripItem[];
selectedItemId: string | null;
onSelectItem: (item: DayStripItem) => void;
onDeleteUnscheduled?: (item: DayStripItem) => void;
treatmentCatalog: TreatmentCatalogEntry[];
loading?: boolean;
}
export function AppointmentsStrip({
stripHidden,
onToggleStripHidden,
selectedDay,
onSelectDay,
items,
selectedItemId,
onSelectItem,
onDeleteUnscheduled,
treatmentCatalog,
loading = false,
}: AppointmentsStripProps) {
const t = useTranslations('treatment');
if (stripHidden) {
return (
<Card className="flex items-center justify-between gap-3 flex-wrap" padding="sm">
<p className="text-sm text-text-secondary">{t('hiddenMessage')}</p>
<button
type="button"
onClick={onToggleStripHidden}
className="text-sm font-medium text-primary hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 rounded-[var(--radius-sm)]"
>
{t('showAppointments')}
</button>
</Card>
);
}
const timed = items.filter((item) => item.kind === 'appointment');
const unscheduled = items.filter((item) => item.kind === 'unscheduled');
return (
<Card className="space-y-4">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] sm:items-center">
<div className="flex min-w-0 items-center gap-2">
<CalendarDays className="w-5 h-5 text-text-muted shrink-0 icon-flat" aria-hidden />
<h2 className="text-sm font-semibold text-text-primary truncate">{t('appointmentsTitle')}</h2>
</div>
<div className="flex min-w-0 justify-center">
<ScheduleDayPicker
compact
className="max-w-sm"
value={selectedDay}
onChange={(d) => onSelectDay(startOfLocalDay(d))}
/>
</div>
<div className="flex min-w-0 items-center justify-end gap-3">
{loading ? (
<p className="text-sm text-text-muted">{t('loadingAppointments')}</p>
) : null}
<button
type="button"
onClick={onToggleStripHidden}
className="text-sm font-medium text-primary hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 rounded-[var(--radius-sm)]"
>
{t('hideAppointments')}
</button>
</div>
</div>
<div className="space-y-3">
<div className="flex flex-wrap gap-2">
{timed.length === 0 && unscheduled.length === 0 && (
<p className="text-sm text-text-muted">{t('emptyDay')}</p>
)}
{timed.map((item) => (
<DayStripCard
key={`${item.kind}-${item.id}`}
item={item}
selected={item.id === selectedItemId}
treatmentCatalog={treatmentCatalog}
onSelect={() => onSelectItem(item)}
/>
))}
</div>
{unscheduled.length > 0 ? (
<div className="space-y-2">
<p className="text-[10px] uppercase tracking-wide text-text-muted">{t('unscheduledHeading')}</p>
<div className="flex flex-wrap gap-2">
{unscheduled.map((item) => (
<DayStripCard
key={`${item.kind}-${item.id}`}
item={item}
selected={item.id === selectedItemId}
treatmentCatalog={treatmentCatalog}
onSelect={() => onSelectItem(item)}
canDelete={item.canDelete}
onDelete={
item.canDelete && onDeleteUnscheduled
? () => onDeleteUnscheduled(item)
: undefined
}
deleteAriaLabel={t('deleteEmptyTreatment')}
/>
))}
</div>
</div>
) : null}
</div>
</Card>
);
}