'use client'; import { useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import Link from 'next/link'; import { apiFetch } from '../../../lib/api'; import { useSession } from '../../../components/session-provider'; interface Task { id: string; title: string; status: string; priority: string; dueDate: string | null; assignedTo: string | null; } interface Obligation { id: string; title: string; status: string; priority: string | null; dueDate: string | null; obligationType: string | null; recurrence: string | null; } interface Contract { id: string; title: string; status: string; expiresAt: string | null; counterpartyName: string | null; } interface Goal { id: string; title: string; status: string; targetDate: string | null; } type CalEvent = { id: string; type: 'task' | 'obligation' | 'contract' | 'goal'; title: string; date: Date; priority?: string; extra?: string; href: string; }; const TYPE_ICON: Record = { task: '✅', obligation: '⚖️', contract: '📝', goal: '🎯', }; const TYPE_CLS: Record = { task: 'border-l-primary', obligation: 'border-l-warn', contract: 'border-l-signal-ok', goal: 'border-l-violet-500', }; const MONTHS = ['Ian','Feb','Mar','Apr','Mai','Iun','Iul','Aug','Sep','Oct','Nov','Dec']; function daysUntil(d: Date) { return Math.ceil((d.getTime() - Date.now()) / 86400_000); } export default function CalendarPage() { const { activeTenant } = useSession(); const tenantId = activeTenant?.tenantId ?? ''; const [view, setView] = useState<'upcoming' | 'overdue' | 'month'>('upcoming'); const now = new Date(); const [monthOffset, setMonthOffset] = useState(0); const { data: tasks = [] } = useQuery({ queryKey: ['tasks-cal', tenantId], queryFn: () => apiFetch('/v1/tasks?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000, }); const { data: obligations = [] } = useQuery({ queryKey: ['obligations-cal', tenantId], queryFn: () => apiFetch('/v1/obligations?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000, }); const { data: contracts = [] } = useQuery({ queryKey: ['contracts-cal', tenantId], queryFn: () => apiFetch('/v1/contracts?status=active&limit=100', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000, }); const { data: goals = [] } = useQuery({ queryKey: ['goals-cal', tenantId], queryFn: () => apiFetch('/v1/goals?status=active&limit=100', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000, }); const events: CalEvent[] = useMemo(() => { const evts: CalEvent[] = []; for (const t of tasks) { if (t.dueDate && t.status !== 'completed' && t.status !== 'cancelled') { evts.push({ id: t.id, type: 'task', title: t.title, date: new Date(t.dueDate), priority: t.priority, href: '/dashboard/tasks' }); } } for (const o of obligations) { if (o.dueDate && o.status !== 'completed') { evts.push({ id: o.id, type: 'obligation', title: o.title, date: new Date(o.dueDate), priority: o.priority ?? 'medium', extra: o.obligationType ?? undefined, href: '/dashboard/obligations' }); } } for (const c of contracts) { if (c.expiresAt) { evts.push({ id: c.id, type: 'contract', title: c.title, date: new Date(c.expiresAt), extra: c.counterpartyName ?? undefined, href: '/dashboard/contracts' }); } } for (const g of goals) { if (g.targetDate) { evts.push({ id: g.id, type: 'goal', title: g.title, date: new Date(g.targetDate), href: '/dashboard/goals' }); } } return evts.sort((a, b) => a.date.getTime() - b.date.getTime()); }, [tasks, obligations, contracts, goals]); const overdue = events.filter((e) => e.date < now); const upcoming = events.filter((e) => e.date >= now && daysUntil(e.date) <= 90); // Month view data const viewDate = new Date(now.getFullYear(), now.getMonth() + monthOffset, 1); const viewYear = viewDate.getFullYear(); const viewMonth = viewDate.getMonth(); const firstDow = new Date(viewYear, viewMonth, 1).getDay(); const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate(); const monthEvents: Record = {}; for (const e of events) { if (e.date.getFullYear() === viewYear && e.date.getMonth() === viewMonth) { const day = e.date.getDate(); monthEvents[day] = [...(monthEvents[day] ?? []), e]; } } return (

Calendar & Termene

{overdue.length > 0 && {overdue.length} depășite · } {upcoming.length} termene în 90 zile · {events.length} total

{(['upcoming', 'overdue', 'month'] as const).map((v) => ( ))}
{/* Legend */}
{Object.entries(TYPE_ICON).map(([type, icon]) => (
{icon} {type}
))}
{view === 'upcoming' && (
{upcoming.length === 0 ? (

🗓️

Niciun termen în 90 zile.

) : (
{upcoming.map((e) => { const days = daysUntil(e.date); return ( {TYPE_ICON[e.type]}

{e.title}

{e.extra &&

{e.extra}

}

{e.date.toLocaleDateString('ro-RO', { day: '2-digit', month: 'short' })}

{days === 0 ? 'azi' : days === 1 ? 'mâine' : `${days}z`}

); })}
)}
)} {view === 'overdue' && (
{overdue.length === 0 ? (

Nicio depășire. Excelent.

) : (
{[...overdue].reverse().map((e) => { const days = Math.abs(daysUntil(e.date)); return ( {TYPE_ICON[e.type]}

{e.title}

{e.extra &&

{e.extra}

}

{e.date.toLocaleDateString('ro-RO', { day: '2-digit', month: 'short' })}

−{days}z

); })}
)}
)} {view === 'month' && (
{/* Month nav */}
{MONTHS[viewMonth]} {viewYear}
{/* Calendar grid */}
{['Du','Lu','Ma','Mi','Jo','Vi','Sâ'].map((d) => (
{d}
))}
{/* Empty cells for first dow */} {Array.from({ length: firstDow }).map((_, i) => (
))} {Array.from({ length: daysInMonth }, (_, i) => i + 1).map((day) => { const isToday = day === now.getDate() && viewMonth === now.getMonth() && viewYear === now.getFullYear(); const dayEvents = monthEvents[day] ?? []; return (

{day}

{dayEvents.slice(0, 3).map((e) => ( {TYPE_ICON[e.type]} {e.title} ))} {dayEvents.length > 3 && (

+{dayEvents.length - 3}

)}
); })}
)}
); }