2026-05-07 03:40:29 +03:30
|
|
|
'use client';
|
|
|
|
|
|
2026-08-19 15:05:58 +03:30
|
|
|
import { useTranslations } from 'next-intl';
|
2026-05-07 03:40:29 +03:30
|
|
|
import { CalendarDays } from 'lucide-react';
|
2026-05-18 12:31:21 +03:30
|
|
|
import { Card } from '@/components/ui/shared/Card';
|
|
|
|
|
import { ScheduleDayPicker } from '@/components/ui/shared/ScheduleDayPicker';
|
2026-05-18 14:08:07 +03:30
|
|
|
import { startOfLocalDay } from '@/components/appointments/appointmentTime';
|
2026-08-19 15:05:58 +03:30
|
|
|
import { DayStripCard } from '@/components/ui/treatment/DayStripCard';
|
|
|
|
|
import type { DayStripItem } from '@/components/treatment/dayStrip';
|
2026-07-06 20:40:19 +03:30
|
|
|
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
|
2026-05-07 03:40:29 +03:30
|
|
|
|
|
|
|
|
interface AppointmentsStripProps {
|
|
|
|
|
stripHidden: boolean;
|
|
|
|
|
onToggleStripHidden: () => void;
|
|
|
|
|
selectedDay: Date;
|
|
|
|
|
onSelectDay: (day: Date) => void;
|
2026-08-19 15:05:58 +03:30
|
|
|
items: DayStripItem[];
|
|
|
|
|
selectedItemId: string | null;
|
|
|
|
|
onSelectItem: (item: DayStripItem) => void;
|
|
|
|
|
onDeleteUnscheduled?: (item: DayStripItem) => void;
|
2026-07-06 20:40:19 +03:30
|
|
|
treatmentCatalog: TreatmentCatalogEntry[];
|
2026-05-07 03:40:29 +03:30
|
|
|
loading?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function AppointmentsStrip({
|
|
|
|
|
stripHidden,
|
|
|
|
|
onToggleStripHidden,
|
|
|
|
|
selectedDay,
|
|
|
|
|
onSelectDay,
|
2026-08-19 15:05:58 +03:30
|
|
|
items,
|
|
|
|
|
selectedItemId,
|
|
|
|
|
onSelectItem,
|
|
|
|
|
onDeleteUnscheduled,
|
2026-07-06 20:40:19 +03:30
|
|
|
treatmentCatalog,
|
2026-05-07 03:40:29 +03:30
|
|
|
loading = false,
|
|
|
|
|
}: AppointmentsStripProps) {
|
2026-06-20 14:51:43 +03:30
|
|
|
const t = useTranslations('treatment');
|
|
|
|
|
|
2026-05-07 03:40:29 +03:30
|
|
|
if (stripHidden) {
|
|
|
|
|
return (
|
2026-05-08 13:56:33 +03:30
|
|
|
<Card className="flex items-center justify-between gap-3 flex-wrap" padding="sm">
|
2026-06-20 14:51:43 +03:30
|
|
|
<p className="text-sm text-text-secondary">{t('hiddenMessage')}</p>
|
2026-05-07 03:40:29 +03:30
|
|
|
<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)]"
|
|
|
|
|
>
|
2026-06-20 14:51:43 +03:30
|
|
|
{t('showAppointments')}
|
2026-05-07 03:40:29 +03:30
|
|
|
</button>
|
2026-05-08 13:56:33 +03:30
|
|
|
</Card>
|
2026-05-07 03:40:29 +03:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-19 15:05:58 +03:30
|
|
|
const timed = items.filter((item) => item.kind === 'appointment');
|
|
|
|
|
const unscheduled = items.filter((item) => item.kind === 'unscheduled');
|
|
|
|
|
|
2026-05-07 03:40:29 +03:30
|
|
|
return (
|
2026-05-08 13:56:33 +03:30
|
|
|
<Card className="space-y-4">
|
2026-08-19 23:59:37 +03:30
|
|
|
<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">
|
2026-05-07 03:40:29 +03:30
|
|
|
<CalendarDays className="w-5 h-5 text-text-muted shrink-0 icon-flat" aria-hidden />
|
2026-06-20 14:51:43 +03:30
|
|
|
<h2 className="text-sm font-semibold text-text-primary truncate">{t('appointmentsTitle')}</h2>
|
2026-05-07 03:40:29 +03:30
|
|
|
</div>
|
2026-08-19 23:59:37 +03:30
|
|
|
<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>
|
2026-05-07 03:40:29 +03:30
|
|
|
</div>
|
|
|
|
|
|
2026-08-19 15:05:58 +03:30
|
|
|
<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}
|
2026-05-07 03:40:29 +03:30
|
|
|
</div>
|
2026-05-08 13:56:33 +03:30
|
|
|
</Card>
|
2026-05-07 03:40:29 +03:30
|
|
|
);
|
|
|
|
|
}
|