diff --git a/src/app/dashboard/promises/page.tsx b/src/app/dashboard/promises/page.tsx new file mode 100644 index 0000000..f820568 --- /dev/null +++ b/src/app/dashboard/promises/page.tsx @@ -0,0 +1,185 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { apiFetch } from '../../../lib/api'; +import { useSession } from '../../../components/session-provider'; + +interface Task { + id: string; title: string; description: string | null; status: string; + priority: string | null; dueDate: string | null; tags: string[]; createdAt: string; +} + +const PROMISE_TAGS = ['commitment', 'promise', 'angajament', 'promisiune', 'obligatie', 'obligation']; + +function isPromise(task: Task) { + const tags = task.tags.map((t) => t.toLowerCase()); + const title = task.title.toLowerCase(); + return tags.some((t) => PROMISE_TAGS.includes(t)) || + title.includes('promit') || title.includes('ma angajez') || title.includes('commit'); +} + +function daysUntil(d: string) { return Math.ceil((new Date(d).getTime() - Date.now()) / 86400_000); } + +const PRIORITY_CLS: Record = { + urgent: 'text-signal-danger', + high: 'text-warn', + medium: 'text-ink', + low: 'text-ink-faint', +}; + +export default function PromisesPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const qc = useQueryClient(); + const [showCreate, setShowCreate] = useState(false); + const [statusFilter, setStatusFilter] = useState<'active' | 'kept' | 'all'>('active'); + const [form, setForm] = useState({ title: '', description: '', dueDate: '', priority: 'high' }); + + const { data: allTasks = [], isLoading } = useQuery({ + queryKey: ['promises-tasks', tenantId], + queryFn: () => apiFetch('/v1/tasks?limit=500', { tenantId }), + enabled: Boolean(tenantId), staleTime: 30_000, + }); + + const promises = useMemo(() => allTasks.filter(isPromise), [allTasks]); + + const active = promises.filter((t) => t.status !== 'completed' && t.status !== 'cancelled'); + const kept = promises.filter((t) => t.status === 'completed'); + const overdue = active.filter((t) => t.dueDate && daysUntil(t.dueDate) < 0); + + const shown = statusFilter === 'active' ? active : statusFilter === 'kept' ? kept : promises; + + const completeMut = useMutation({ + mutationFn: (id: string) => apiFetch(`/v1/tasks/${id}`, { tenantId, method: 'PATCH', body: { status: 'completed' } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ['promises-tasks', tenantId] }), + }); + + const createMut = useMutation({ + mutationFn: () => apiFetch('/v1/tasks', { tenantId, method: 'POST', body: { + ...form, tags: ['commitment', 'promise'], dueDate: form.dueDate || null, + }}), + onSuccess: () => { qc.invalidateQueries({ queryKey: ['promises-tasks', tenantId] }); setShowCreate(false); setForm({ title: '', description: '', dueDate: '', priority: 'high' }); }, + }); + + return ( +
+
+
+

Angajamente & Promisiuni

+

+ {isLoading ? 'Se încarcă…' : `${active.length} active · ${kept.length} onorate`} + {overdue.length > 0 && · {overdue.length} depășite} +

+
+ +
+ + {overdue.length > 0 && ( +
+

⚠ Angajamente depășite ({overdue.length})

+ {overdue.map((t) => ( +
+ {t.title} + + {t.dueDate ? `${Math.abs(daysUntil(t.dueDate))} zile întârziere` : ''} + +
+ ))} +
+ )} + + {showCreate && ( +
+

Angajament nou

+ setForm((p) => ({ ...p, title: e.target.value }))} + className="w-full rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" /> +