diff --git a/src/app/dashboard/routines/page.tsx b/src/app/dashboard/routines/page.tsx new file mode 100644 index 0000000..5086052 --- /dev/null +++ b/src/app/dashboard/routines/page.tsx @@ -0,0 +1,227 @@ +'use client'; + +import { useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { apiFetch } from '../../../lib/api'; +import { useSession } from '../../../components/session-provider'; + +interface Task { + id: string; title: string; status: string; priority: string; + dueDate: string | null; tags: string[]; assignedTo: string | null; + description: string | null; createdAt: string; +} + +const PRIORITY_DOT: Record = { + critical: 'bg-signal-danger', high: 'bg-warn', + medium: 'bg-primary', low: 'bg-muted-foreground', +}; + +const ROUTINE_TAGS = ['routine', 'daily', 'weekly', 'monthly', 'recurring', 'habit']; + +function isRoutine(task: Task) { + return task.tags.some((t) => ROUTINE_TAGS.includes(t.toLowerCase())); +} + +const TODAY_LABEL: Record = { + Mon: 'Lu', Tue: 'Ma', Wed: 'Mi', Thu: 'Jo', Fri: 'Vi', Sat: 'Sâ', Sun: 'Du', +}; + +export default function RoutinesPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const qc = useQueryClient(); + + const [tab, setTab] = useState<'routines' | 'all'>('routines'); + const [showCreate, setShowCreate] = useState(false); + const [form, setForm] = useState({ title: '', priority: 'medium', dueDate: '', tags: 'routine,daily', description: '' }); + + const { data: tasks = [], isLoading } = useQuery({ + queryKey: ['tasks-routines', tenantId], + queryFn: () => apiFetch('/v1/tasks?limit=200', { tenantId }), + enabled: Boolean(tenantId), staleTime: 30_000, + }); + + const completeMut = useMutation({ + mutationFn: (id: string) => + apiFetch(`/v1/tasks/${id}`, { tenantId, method: 'PATCH', body: { status: 'completed' } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ['tasks-routines', tenantId] }), + }); + + const createMut = useMutation({ + mutationFn: (body: Record) => + apiFetch('/v1/tasks', { tenantId, method: 'POST', body }), + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['tasks-routines', tenantId] }); + setShowCreate(false); + setForm({ title: '', priority: 'medium', dueDate: '', tags: 'routine,daily', description: '' }); + }, + }); + + const routines = tasks.filter(isRoutine); + const active = routines.filter((t) => t.status !== 'completed' && t.status !== 'cancelled'); + const completed = routines.filter((t) => t.status === 'completed'); + + const today = new Date().toLocaleDateString('en-US', { weekday: 'short' }); + const todayLabel = TODAY_LABEL[today] ?? today; + + // All active tasks for the "all tasks" view + const allActive = tasks.filter((t) => t.status !== 'completed' && t.status !== 'cancelled'); + const overdueAll = allActive.filter((t) => t.dueDate && new Date(t.dueDate) < new Date()); + + return ( +
+
+
+

Tasks & Rutine

+

+ {active.length} rutine active · {todayLabel} · {allActive.length} taskuri totale + {overdueAll.length > 0 && {overdueAll.length} depășite} +

+
+ +
+ + {/* Tabs */} +
+ + +
+ + {/* Create form */} + {showCreate && ( +
+

Task / rutină nouă

+ setForm({ ...form, title: e.target.value })} + className="w-full rounded-lg border bg-card px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" /> +
+ + setForm({ ...form, dueDate: e.target.value })} + className="rounded-lg border bg-card px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" /> +
+ setForm({ ...form, tags: e.target.value })} + className="w-full rounded-lg border bg-card px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" /> + setForm({ ...form, description: e.target.value })} + className="w-full rounded-lg border bg-card px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" /> +
+ + +
+
+ )} + + {isLoading ? ( +
Se încarcă…
+ ) : tab === 'routines' ? ( + <> + {active.length === 0 && ( +
+

🔄

+

Nicio rutină activă. Adaugă un task cu tag-ul "routine" sau "daily".

+
+ )} + {active.length > 0 && ( +
+

Active ({active.length})

+
+ {active.map((t) => { + const overdue = t.dueDate && new Date(t.dueDate) < new Date(); + return ( +
+
+
+ )} + {completed.length > 0 && ( +
+

Completate recent ({completed.length})

+
+ {completed.slice(0, 5).map((t) => ( +
+
+

{t.title}

+
+ ))} +
+
+ )} + + ) : ( + /* All tasks view */ +
+ {overdueAll.length > 0 && ( +
+

Depășite ({overdueAll.length})

+
+ {overdueAll.map((t) => ( +
+
+
+ )} +
+ {allActive.filter((t) => !overdueAll.includes(t)).map((t) => ( +
+
+
+ )} +
+ ); +}