'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 Goal { id: string; title: string; description: string | null; status: string; progress: number | null; tags: string[]; targetDate: string | null; createdAt: string; } const OKR_TAG = 'okr'; const KR_TAG = 'key-result'; const QUARTER = `Q${Math.ceil((new Date().getMonth() + 1) / 3)}-${new Date().getFullYear()}`; export default function OKRPage() { const { activeTenant } = useSession(); const tenantId = activeTenant?.tenantId ?? ''; const qc = useQueryClient(); const [showAddO, setShowAddO] = useState(false); const [showAddKR, setShowAddKR] = useState(null); const [oForm, setOForm] = useState({ title: '', description: '' }); const [krForm, setKrForm] = useState({ title: '', targetDate: '' }); const { data: goals = [], isLoading } = useQuery({ queryKey: ['okr', tenantId], queryFn: () => apiFetch('/v1/goals?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000, }); const objectives = useMemo(() => goals.filter((g) => g.tags.includes(OKR_TAG) && !g.tags.includes(KR_TAG)), [goals]); const keyResults = useMemo(() => { const map: Record = {}; for (const g of goals) { if (!g.tags.includes(KR_TAG)) continue; const oId = g.tags.find((t) => t.startsWith('obj-')); if (oId) { map[oId] = [...(map[oId] ?? []), g]; } } return map; }, [goals]); function objProgress(oId: string): number { const krs = keyResults[`obj-${oId}`] ?? []; if (krs.length === 0) return 0; return Math.round(krs.reduce((s, k) => s + (k.progress ?? 0), 0) / krs.length); } const addObjMut = useMutation({ mutationFn: () => apiFetch('/v1/goals', { tenantId, method: 'POST', body: { title: oForm.title, description: oForm.description || undefined, tags: [OKR_TAG, QUARTER], status: 'active', progress: 0, }}), onSuccess: () => { qc.invalidateQueries({ queryKey: ['okr', tenantId] }); setShowAddO(false); setOForm({ title: '', description: '' }); }, }); const addKRMut = useMutation({ mutationFn: (objectiveId: string) => apiFetch('/v1/goals', { tenantId, method: 'POST', body: { title: krForm.title, tags: [KR_TAG, `obj-${objectiveId}`, QUARTER], status: 'active', progress: 0, targetDate: krForm.targetDate || undefined, }}), onSuccess: () => { qc.invalidateQueries({ queryKey: ['okr', tenantId] }); setShowAddKR(null); setKrForm({ title: '', targetDate: '' }); }, }); const patchKRMut = useMutation({ mutationFn: ({ id, progress }: { id: string; progress: number }) => apiFetch(`/v1/goals/${id}`, { tenantId, method: 'PATCH', body: { progress } }), onSuccess: () => qc.invalidateQueries({ queryKey: ['okr', tenantId] }), }); function progressColor(p: number) { return p >= 70 ? 'bg-signal-ok' : p >= 40 ? 'bg-warn' : 'bg-signal-danger'; } return (

OKR Tracker

{QUARTER} · {objectives.length} Objectives · {Object.values(keyResults).flat().length} Key Results

{showAddO && (

Obiectiv nou ({QUARTER})

setOForm((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" />