import { useEffect, useState } from "react"; import { INITIAL_DB } from "../../model/seed.js"; import { STORAGE_KEY, TODAY } from "../../model/schema.js"; import { clone } from "../../lib/utils.js"; import { normalizeDb } from "../../lib/validation.js"; function loadDb() { if (typeof window === "undefined") return clone(INITIAL_DB); try { const raw = window.localStorage.getItem(STORAGE_KEY); if (!raw) return clone(INITIAL_DB); return normalizeDb(JSON.parse(raw)); } catch { return clone(INITIAL_DB); } } export function useSoc2Registry() { const [db, setDb] = useState(() => loadDb()); const [lastSavedAt, setLastSavedAt] = useState(null); const [importError, setImportError] = useState(""); useEffect(() => { if (typeof window === "undefined") return; window.localStorage.setItem(STORAGE_KEY, JSON.stringify(db, null, 2)); setLastSavedAt(new Date().toLocaleString("fr-CA")); }, [db]); const updateMilestoneStage = (pillarId, milestoneId, nextStage) => { setDb((current) => ({ ...current, pillars: current.pillars.map((pillar) => pillar.id !== pillarId ? pillar : { ...pillar, milestones: pillar.milestones.map((milestone) => milestone.id === milestoneId ? { ...milestone, stage: nextStage } : milestone ), } ), })); }; const updateEvidence = (evidenceId, patch) => { setDb((current) => ({ ...current, evidence: current.evidence.map((item) => item.id === evidenceId ? { ...item, ...patch, updatedAt: patch.updatedAt || TODAY } : item ), })); }; const toggleEvidenceMilestoneLink = (evidenceId, pillarId, milestoneId) => { setDb((current) => { const evidence = current.evidence.find((item) => item.id === evidenceId); if (!evidence) return current; const hasLink = evidence.linkedMilestones.includes(milestoneId); return { ...current, evidence: current.evidence.map((item) => item.id !== evidenceId ? item : { ...item, linkedMilestones: hasLink ? item.linkedMilestones.filter((id) => id !== milestoneId) : [...item.linkedMilestones, milestoneId], updatedAt: TODAY, } ), pillars: current.pillars.map((pillar) => pillar.id !== pillarId ? pillar : { ...pillar, milestones: pillar.milestones.map((milestone) => { if (milestone.id !== milestoneId) return milestone; const alreadyLinked = milestone.evidenceRefs.includes(evidenceId); return { ...milestone, evidenceRefs: hasLink ? milestone.evidenceRefs.filter((id) => id !== evidenceId) : alreadyLinked ? milestone.evidenceRefs : [...milestone.evidenceRefs, evidenceId], }; }), } ), }; }); }; const createEvidence = (selectedPillar) => { const maxId = db.evidence .map((item) => Number(String(item.id).replace("ev-", ""))) .filter((n) => Number.isFinite(n)) .reduce((max, n) => Math.max(max, n), 0); const nextId = `ev-${String(maxId + 1).padStart(3, "0")}`; const seed = { id: nextId, title: "Nouvelle preuve", category: "General", pillar: selectedPillar, owner: "Ops", freshness: "Brouillon", updatedAt: TODAY, auditorReady: false, visibility: "internal", source: "Source à définir", tags: [], linkedMilestones: [], summary: "", }; setDb((current) => ({ ...current, evidence: [seed, ...current.evidence] })); return nextId; }; const deleteEvidence = (evidenceId) => { setDb((current) => ({ ...current, evidence: current.evidence.filter((item) => item.id !== evidenceId), pillars: current.pillars.map((pillar) => ({ ...pillar, milestones: pillar.milestones.map((milestone) => ({ ...milestone, evidenceRefs: milestone.evidenceRefs.filter((ref) => ref !== evidenceId), })), })), auditorPacks: current.auditorPacks.map((pack) => ({ ...pack, requiredEvidence: pack.requiredEvidence.filter((ref) => ref !== evidenceId), })), })); }; const exportDb = () => { const blob = new Blob([JSON.stringify(db, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "chezlepro-soc2-cockpit.json"; a.click(); URL.revokeObjectURL(url); }; const copyDb = async () => { try { await navigator.clipboard.writeText(JSON.stringify(db, null, 2)); } catch { // clipboard may be unavailable } }; const importDb = (payload) => { setImportError(""); try { const parsed = JSON.parse(payload); const normalized = normalizeDb(parsed); setDb(normalized); return { ok: true, data: normalized }; } catch { const error = "Import invalide : le JSON n’a pas pu être interprété."; setImportError(error); return { ok: false, error }; } }; const resetDemo = () => { setDb(clone(INITIAL_DB)); }; return { db, lastSavedAt, importError, updateMilestoneStage, updateEvidence, toggleEvidenceMilestoneLink, createEvidence, deleteEvidence, exportDb, copyDb, importDb, resetDemo, }; }