diff --git a/src/app/dashboard/standup/page.tsx b/src/app/dashboard/standup/page.tsx new file mode 100644 index 0000000..597deb2 --- /dev/null +++ b/src/app/dashboard/standup/page.tsx @@ -0,0 +1,163 @@ +'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; status: string; priority: string | null; tags: string[]; updatedAt: string; createdAt: string; } +interface Observation { id: string; metric: string; value: string; createdAt: string; } + +const TODAY = new Date().toISOString().slice(0, 10); + +export default function StandupPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const qc = useQueryClient(); + + const [yesterday, setYesterday] = useState(''); + const [today, setToday] = useState(''); + const [blockers, setBlockers] = useState(''); + const [mood, setMood] = useState(3); + const [submitted, setSubmitted] = useState(false); + + const { data: tasks = [] } = useQuery({ queryKey: ['standup-tasks', tenantId], queryFn: () => apiFetch('/v1/tasks?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); + const { data: observations = [] } = useQuery({ queryKey: ['standup-obs', tenantId], queryFn: () => apiFetch('/v1/observations', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 }); + + const completedToday = useMemo(() => + tasks.filter((t) => t.status === 'completed' && t.updatedAt.startsWith(TODAY)), + [tasks]); + + const todayObs = useMemo(() => + observations.filter((o) => o.createdAt.startsWith(TODAY)), + [observations]); + + const upcoming = useMemo(() => + tasks.filter((t) => !['completed','cancelled'].includes(t.status) && t.priority === 'urgent').slice(0, 5), + [tasks]); + + const submitMut = useMutation({ + mutationFn: async () => { + const promises = []; + if (yesterday.trim()) { + promises.push(apiFetch('/v1/observations', { tenantId, method: 'POST', body: { + metric: 'standup-yesterday', value: yesterday.trim(), + subjectType: 'daily-log', confidence: 1, observedAt: new Date().toISOString(), + }})); + } + if (today.trim()) { + promises.push(apiFetch('/v1/observations', { tenantId, method: 'POST', body: { + metric: 'standup-today', value: today.trim(), + subjectType: 'daily-log', confidence: 1, observedAt: new Date().toISOString(), + }})); + } + if (blockers.trim()) { + promises.push(apiFetch('/v1/observations', { tenantId, method: 'POST', body: { + metric: 'standup-blockers', value: blockers.trim(), + subjectType: 'daily-log', confidence: 1, observedAt: new Date().toISOString(), + }})); + } + promises.push(apiFetch('/v1/observations', { tenantId, method: 'POST', body: { + metric: 'energy-level', value: String(mood), + unit: '/5', subjectType: 'personal', confidence: 1, observedAt: new Date().toISOString(), + }})); + await Promise.all(promises); + }, + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['standup-obs', tenantId] }); + setSubmitted(true); + }, + }); + + const dateLabel = new Date().toLocaleDateString('ro-RO', { weekday: 'long', day: 'numeric', month: 'long' }); + const MOODS = ['😴', '😐', '🙂', '😊', '🚀']; + + return ( +
+
+

Daily Standup

+

{dateLabel}

+
+ + {submitted ? ( +
+

+

Standup salvat!

+

Răspunsurile au fost logate ca observații în CEO OS.

+ +
+ ) : ( + <> + {/* Context from CEO OS */} + {(completedToday.length > 0 || todayObs.length > 0) && ( +
+

Din CEO OS azi

+ {completedToday.length > 0 &&

✓ {completedToday.length} task-uri finalizate astăzi

} + {todayObs.length > 0 &&

📊 {todayObs.length} observații logate

} +
+ )} + + {/* Standup form */} +
+
+ +