117 lines
4.4 KiB
TypeScript
117 lines
4.4 KiB
TypeScript
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
import { AlertTriangle } from 'lucide-react';
|
|
import { Button } from '@/components/ui/shared/Button';
|
|
import { DetailLabSendBadge } from '@/components/ui/treatment/DetailLabSendBadge';
|
|
import { TreatmentTypeBadge } from '@/components/ui/treatment/TreatmentTypeBadge';
|
|
import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentTypeDisplay';
|
|
import type { LabDispatchAttentionItem } from '@/components/treatment/labDispatchAttention';
|
|
import type { LinkedOrganizationOption } from '@/types/treatment';
|
|
import type { TreatmentCatalogEntry } from '@/types/treatment-catalog';
|
|
|
|
interface LabDispatchAttentionPanelProps {
|
|
items: LabDispatchAttentionItem[];
|
|
treatmentCatalog: TreatmentCatalogEntry[];
|
|
labDependentCodes: Set<string>;
|
|
orgs?: LinkedOrganizationOption[];
|
|
onGoToDispatch: (item: LabDispatchAttentionItem) => void;
|
|
compact?: boolean;
|
|
}
|
|
|
|
export function LabDispatchAttentionPanel({
|
|
items,
|
|
treatmentCatalog,
|
|
labDependentCodes,
|
|
orgs,
|
|
onGoToDispatch,
|
|
compact = false,
|
|
}: LabDispatchAttentionPanelProps) {
|
|
const t = useTranslations('treatment');
|
|
|
|
if (items.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className={compact ? 'space-y-2 pt-2' : 'surface-card p-4 space-y-3 border border-amber-500/35 bg-amber-500/5'}>
|
|
{!compact ? (
|
|
<div className="flex items-start gap-2">
|
|
<AlertTriangle className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5 icon-flat" />
|
|
<div className="min-w-0">
|
|
<h3 className="text-sm font-semibold text-text-primary">{t('labAttentionTitle')}</h3>
|
|
<p className="text-[11px] text-text-muted mt-0.5">{t('labAttentionSubtitle')}</p>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
|
|
<ul
|
|
className={`space-y-1.5 overflow-y-auto overscroll-y-contain pr-1 ${
|
|
compact ? 'max-h-[min(200px,30vh)]' : 'max-h-[min(240px,35vh)]'
|
|
}`}
|
|
>
|
|
{items.map((item) => {
|
|
const teeth = item.detail.teeth.length
|
|
? [...item.detail.teeth].sort().join(', ')
|
|
: t('teethNone');
|
|
const dateLabel = new Date(item.treatmentAt).toLocaleDateString(undefined, {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
});
|
|
|
|
return (
|
|
<li
|
|
key={item.key}
|
|
className={`flex flex-col gap-1.5 rounded-[var(--radius-sm)] border border-border/60 bg-background-secondary/40 px-2 py-1.5 sm:flex-row sm:items-center sm:justify-between ${
|
|
compact ? '' : ''
|
|
}`}
|
|
>
|
|
<div className="min-w-0 space-y-1">
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
<time className="text-[11px] font-medium text-text-primary tabular-nums">
|
|
{dateLabel}
|
|
</time>
|
|
{item.isCurrentDraft ? (
|
|
<span className="text-[10px] uppercase tracking-wide text-primary font-medium">
|
|
{t('labAttentionCurrentDraft')}
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-1.5 min-w-0">
|
|
<span className="text-[11px] text-text-muted tabular-nums">
|
|
{t('detailLabel', { n: item.detailNumber })}
|
|
</span>
|
|
<TreatmentTypeBadge
|
|
type={item.detail.treatmentType}
|
|
label={treatmentTypeLabelFromCatalog(
|
|
item.detail.treatmentType,
|
|
treatmentCatalog,
|
|
)}
|
|
/>
|
|
<DetailLabSendBadge
|
|
detail={item.detail}
|
|
labDependentCodes={labDependentCodes}
|
|
orgs={orgs}
|
|
/>
|
|
</div>
|
|
<p className="text-[11px] text-text-secondary truncate">
|
|
{t('teethLabel')} {teeth}
|
|
</p>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="primary"
|
|
className={`shrink-0 w-full sm:w-auto text-xs ${compact ? 'py-1' : 'py-1.5'}`}
|
|
onClick={() => onGoToDispatch(item)}
|
|
>
|
|
{item.isCurrentDraft ? t('labAttentionGoDispatch') : t('labAttentionLoadDispatch')}
|
|
</Button>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|