From e7b91bd92b1ded110476f1f7a0cfce827a5ec5f7 Mon Sep 17 00:00:00 2001 From: admin-valentin Date: Sat, 1 Aug 2026 20:51:21 +0000 Subject: [PATCH] feat(CC-055): add Decision Workspace page (context/options/assumptions/decision/outcome editor) --- .../dashboard/decisions/workspace/page.tsx | 365 ++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100644 src/app/dashboard/decisions/workspace/page.tsx diff --git a/src/app/dashboard/decisions/workspace/page.tsx b/src/app/dashboard/decisions/workspace/page.tsx new file mode 100644 index 0000000..587ef31 --- /dev/null +++ b/src/app/dashboard/decisions/workspace/page.tsx @@ -0,0 +1,365 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { apiFetch } from '../../../../lib/api'; +import { useSession } from '../../../../components/session-provider'; +import type { Decision } from '../../../../lib/api'; + +export default function DecisionWorkspacePage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const qc = useQueryClient(); + + const [selectedId, setSelectedId] = useState(null); + const [context, setContext] = useState(''); + const [options, setOptions] = useState(['', '']); + const [assumptions, setAssumptions] = useState(['']); + const [selectedOption, setSelectedOption] = useState(''); + const [outcomeReview, setOutcomeReview] = useState(''); + const [reviewDueAt, setReviewDueAt] = useState(''); + const [dirty, setDirty] = useState(false); + const [saving, setSaving] = useState(false); + const [saveError, setSaveError] = useState(null); + + const { data: decisions = [], isLoading } = useQuery({ + queryKey: ['decisions', tenantId], + queryFn: () => apiFetch('/v1/decisions?limit=50', { tenantId }), + enabled: Boolean(tenantId), + }); + + useEffect(() => { + if (!selectedId) return; + const d = decisions.find((d) => d.id === selectedId); + if (!d) return; + setContext(d.context); + setOptions(d.options.length ? d.options : ['', '']); + setAssumptions(d.assumptions.length ? d.assumptions : ['']); + setSelectedOption(d.selectedOption ?? ''); + setOutcomeReview(d.outcomeReview ?? ''); + setReviewDueAt(d.reviewDueAt ? d.reviewDueAt.slice(0, 10) : ''); + setDirty(false); + setSaveError(null); + }, [selectedId, decisions]); + + async function handleNew() { + if (!tenantId) return; + try { + const created = await apiFetch('/v1/decisions', { + method: 'POST', + body: { context: 'Situație nouă — actualizează contextul', options: [], assumptions: [] }, + tenantId, + }); + await qc.invalidateQueries({ queryKey: ['decisions', tenantId] }); + setSelectedId(created.id); + } catch (e) { + console.error('[workspace] create failed', e); + } + } + + async function handleSave() { + if (!selectedId || !tenantId) return; + setSaving(true); + setSaveError(null); + try { + await apiFetch(`/v1/decisions/${selectedId}`, { + method: 'PATCH', + body: { + context, + options: options.filter((o) => o.trim()), + assumptions: assumptions.filter((a) => a.trim()), + selectedOption: selectedOption || null, + ...(outcomeReview ? { outcomeReview } : {}), + ...(reviewDueAt ? { reviewDueAt: new Date(reviewDueAt).toISOString() } : { reviewDueAt: null }), + }, + tenantId, + }); + await qc.invalidateQueries({ queryKey: ['decisions', tenantId] }); + setDirty(false); + } catch (e) { + setSaveError(e instanceof Error ? e.message : 'Eroare la salvare'); + } finally { + setSaving(false); + } + } + + const selected = decisions.find((d) => d.id === selectedId); + const inProgress = decisions.filter((d) => !d.selectedOption); + const decided = decisions.filter((d) => !!d.selectedOption); + const validOptions = options.filter((o) => o.trim()); + + return ( +
+ {/* Sidebar */} + + + {/* Main editor */} + {!selectedId ? ( +
+
+

⚖️

+

+ Selectează o decizie din stânga sau creează una nouă pentru a documenta analiza +

+ +
+
+ ) : ( +
+
+ {/* Header */} +
+
+ + {selected?.selectedOption ? '✓ Decisă' : '○ În analiză'} + + {selected?.reviewDueAt && ( + + Review {new Date(selected.reviewDueAt).toLocaleDateString('ro-RO')} + + )} +
+
+ {saveError && {saveError}} + {dirty ? ( + + ) : ( + Salvat + )} +
+
+ + {/* Context */} +
+ +