'use client' import { useTranslations } from 'next-intl' import { Deadline } from '@/types' import { cn } from '@/lib/utils' import { isDeadlineOverdue, parseDate, startOfDay } from '@/lib/calendar/utils' import { HOVER_REVEAL_CLASS, QUIET_LINK_CLASS } from '@/components/ui/dry-table' import { CalendarClock, Check, FileText, Landmark, Pencil, ReceiptText, Truck, } from 'lucide-react' interface DeadlineRowProps { deadline: Deadline /** Row click: open the edit dialog. */ onEdit?: (deadline: Deadline) => void /** "Markera klar" (or un-done): the list confirms before toggling. */ onRequestToggle: (deadline: Deadline) => void } function daysFromToday(dateStr: string): number { const today = startOfDay(new Date()) const target = parseDate(dateStr) return Math.round((target.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)) } const TYPE_ICONS: Record = { tax: Landmark, invoicing: ReceiptText, delivery: Truck, report: FileText, other: CalendarClock, } // Statutory deadlines carry the receiving authority's mark: Bolagsverket for // the company-law dates, Skatteverket for everything tax-shaped. Non-system // deadlines keep the neutral type icons. const BOLAGSVERKET_TYPES = new Set(['arsredovisning', 'arsstamma']) export function deadlineAuthorityLogo(deadline: Deadline): { src: string; alt: string } | null { if (deadline.tax_deadline_type) { return BOLAGSVERKET_TYPES.has(deadline.tax_deadline_type) ? { src: '/logos/bolagsverket.jpg', alt: 'Bolagsverket' } : { src: '/logos/skatteverket_color.svg', alt: 'Skatteverket' } } // Manual deadlines (including user-created tax-category ones) keep the // neutral type icons: the authority crest is reserved for statutory dates // the system generated from the tax settings. return null } /** The shared 28px round mark: authority logo on white, or a muted type icon. */ export function DeadlineMark({ deadline, completed = false, }: { deadline: Deadline completed?: boolean }) { const logo = deadlineAuthorityLogo(deadline) if (completed) { return ( ) } if (logo) { // Deliberately white in both themes: the authority marks are badges, not // chrome, and their brand colors need a white ground. return ( {logo.alt} ) } const Icon = TYPE_ICONS[deadline.deadline_type] ?? CalendarClock return ( ) } const SWEDISH_MONTHS_SHORT = [ 'jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec', ] export function deadlineDateLabel(dateStr: string): string { const date = parseDate(dateStr) return `${date.getDate()} ${SWEDISH_MONTHS_SHORT[date.getMonth()]}` } /** * One deadline as a concept thread row (scene 17): icon circle, "12 aug · * Title" line, muted status sentence below, quiet hover actions. Rows are * borderless; the section headers carry the structure. */ export function DeadlineRow({ deadline, onEdit, onRequestToggle }: DeadlineRowProps) { const t = useTranslations('deadlines') const overdue = isDeadlineOverdue(deadline) const completed = deadline.is_completed const diff = daysFromToday(deadline.due_date) const relative = completed ? null : diff === 0 ? t('rel_today') : diff === 1 ? t('rel_tomorrow') : diff < 0 ? t('rel_days_ago', { count: Math.abs(diff) }) : diff <= 14 ? t('rel_in_days', { count: diff }) : null const subParts = [ relative, deadline.due_time ? `kl ${deadline.due_time.slice(0, 5)}` : null, deadline.customer?.name ?? null, ].filter(Boolean) as string[] return (
onEdit?.(deadline)} className={cn( 'group flex items-start gap-3 rounded-md py-3 -mx-2 px-2 transition-colors duration-150', onEdit && 'cursor-pointer hover:bg-secondary/35', completed && 'opacity-60', )} >

{deadlineDateLabel(deadline.due_date)} · {deadline.title}

{subParts.length > 0 && (

{subParts.join(' · ')}

)}
{completed ? ( ) : ( )} {onEdit && (
) }