'use client' import { useState } from 'react' import Link from 'next/link' import { useTranslations } from 'next-intl' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { useToast } from '@/components/ui/use-toast' import { Deadline, DeadlineStatus } from '@/types' import { getUpcomingDeadlines, STATUS_LABELS, } from '@/lib/calendar/utils' import { Calendar, ChevronRight, AlertTriangle, Clock, Check, Send, Loader2 } from 'lucide-react' interface UpcomingDeadlinesWidgetProps { deadlines: Deadline[] maxItems?: number onStatusChange?: (deadlineId: string, newStatus: DeadlineStatus) => void } export function UpcomingDeadlinesWidget({ deadlines, maxItems = 5, onStatusChange }: UpcomingDeadlinesWidgetProps) { const { toast } = useToast() const t = useTranslations('upcoming_deadlines') const [updatingId, setUpdatingId] = useState(null) // Get upcoming deadlines (next 7 days) + any needing attention const upcomingDeadlines = getUpcomingDeadlines(deadlines, 7) const actionNeededDeadlines = deadlines.filter( (d) => !d.is_completed && d.status === 'action_needed' ) const overdueDeadlines = deadlines.filter( (d) => !d.is_completed && d.status === 'overdue' ) // Combine and sort (overdue first, then action_needed, then upcoming) const displayDeadlines = [...overdueDeadlines, ...actionNeededDeadlines, ...upcomingDeadlines] .filter((d, i, arr) => arr.findIndex((x) => x.id === d.id) === i) // Remove duplicates .slice(0, maxItems) const overdueCount = overdueDeadlines.length const actionNeededCount = actionNeededDeadlines.length if (displayDeadlines.length === 0) { return null } const formatDate = (dateStr: string) => { const date = new Date(dateStr) const today = new Date() const tomorrow = new Date(today) tomorrow.setDate(tomorrow.getDate() + 1) if (date.toDateString() === today.toDateString()) { return t('today') } if (date.toDateString() === tomorrow.toDateString()) { return t('tomorrow') } return date.toLocaleDateString('sv-SE', { day: 'numeric', month: 'short' }) } const handleStatusChange = async (deadlineId: string, newStatus: DeadlineStatus) => { setUpdatingId(deadlineId) try { const response = await fetch(`/api/deadlines/${deadlineId}/status`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus }), }) if (!response.ok) { const data = await response.json() throw new Error(data.error || 'Failed to update status') } toast({ title: t('toast_status_updated'), description: t('toast_status_updated_description', { status: STATUS_LABELS[newStatus].toLowerCase() }), }) // Notify parent component onStatusChange?.(deadlineId, newStatus) } catch (error) { toast({ title: error instanceof Error ? error.message : t('toast_status_update_failed'), variant: 'destructive', }) } finally { setUpdatingId(null) } } const getStatusBadgeVariant = (status: DeadlineStatus): 'default' | 'secondary' | 'destructive' | 'warning' => { switch (status) { case 'overdue': return 'destructive' case 'action_needed': return 'warning' case 'submitted': case 'confirmed': return 'default' default: return 'secondary' } } return (
{t('title')}
{overdueCount > 0 && ( {t('overdue_badge', { count: overdueCount })} )} {actionNeededCount > 0 && ( {t('action_needed_badge', { count: actionNeededCount })} )}
{displayDeadlines.map((deadline) => { const isUpdating = updatingId === deadline.id return (
{deadline.status === 'overdue' ? ( ) : deadline.status === 'action_needed' ? ( ) : ( )}

{deadline.title}

{formatDate(deadline.due_date)} {deadline.due_time && ` ${t('time_prefix')} ${deadline.due_time.slice(0, 5)}`}

{deadline.tax_deadline_type && ( {t('tax_badge')} )}
{/* Status badge */} {STATUS_LABELS[deadline.status]} {/* Quick action buttons for tax deadlines */} {deadline.tax_deadline_type && !['submitted', 'confirmed'].includes(deadline.status) && ( )} {deadline.status === 'submitted' && ( )}
) })}
) }