diff --git a/src/app/dashboard/reports/exports/page.tsx b/src/app/dashboard/reports/exports/page.tsx new file mode 100644 index 0000000..7f44048 --- /dev/null +++ b/src/app/dashboard/reports/exports/page.tsx @@ -0,0 +1,188 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { apiFetch } from '../../../../lib/api'; +import { useSession } from '../../../../components/session-provider'; + +interface ExportData { + exportedAt: string; schema: string; + data: { + organizations?: unknown[]; + tasks?: unknown[]; + goals?: unknown[]; + decisions?: unknown[]; + transactions?: unknown[]; + contacts?: unknown[]; + risks?: unknown[]; + projects?: unknown[]; + contracts?: unknown[]; + obligations?: unknown[]; + }; + counts: Record; +} + +function downloadJson(data: unknown, filename: string) { + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); + URL.revokeObjectURL(url); +} + +function downloadCsv(rows: string[][], filename: string) { + const bom = ''; + const csv = bom + rows.map((r) => r.map((c) => `"${String(c ?? '').replace(/"/g, '""')}"`).join(',')).join('\n'); + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); + URL.revokeObjectURL(url); +} + +const TODAY = new Date().toISOString().slice(0, 10); + +const EXPORT_SETS: Array<{ + key: string; label: string; icon: string; desc: string; + csvExtract?: (d: ExportData) => { headers: string[]; rows: string[][] }; +}> = [ + { + key: 'full', label: 'Export complet (JSON)', icon: '📦', desc: 'Toate datele platformei în format JSON — Art. 20 GDPR', + csvExtract: undefined, + }, + { + key: 'transactions', label: 'Tranzacții (CSV)', icon: '💳', desc: 'Toate tranzacțiile cu status dovezi — pentru contabil', + csvExtract: (d) => ({ + headers: ['Data', 'Descriere', 'Sumă', 'Monedă', 'Partener', 'Categorie', 'Status dovezi', 'URL dovadă'], + rows: (d.data.transactions ?? []).map((t: any) => [ + t.transactionDate ?? '', t.description ?? '', (parseFloat(t.amountMinorUnits ?? '0') / 100).toFixed(2), + t.currency ?? '', t.counterparty ?? '', t.category ?? '', + t.evidenceStatus ?? '', t.evidenceUrl ?? '', + ]), + }), + }, + { + key: 'contacts', label: 'Contacte (CSV)', icon: '👥', desc: 'Registru CRM — pentru campanii sau mail merge', + csvExtract: (d) => ({ + headers: ['Nume', 'Email', 'Telefon', 'Rol', 'Consimțământ', 'Note'], + rows: (d.data.contacts ?? []).map((c: any) => [ + c.fullName ?? '', c.email ?? '', c.phone ?? '', c.role ?? '', + c.consentStatus ?? '', c.notes ?? '', + ]), + }), + }, + { + key: 'risks', label: 'Riscuri (CSV)', icon: '⚠', desc: 'Registru riscuri — pentru audit sau raport board', + csvExtract: (d) => ({ + headers: ['Titlu', 'Categorie', 'Probabilitate', 'Impact', 'Status', 'Responsabil', 'Data revizuire'], + rows: (d.data.risks ?? []).map((r: any) => [ + r.title ?? '', r.category ?? '', r.probability ?? '', r.impact ?? '', + r.status ?? '', r.owner ?? '', r.reviewDueAt ?? '', + ]), + }), + }, + { + key: 'decisions', label: 'Decizii (CSV)', icon: '🎯', desc: 'Registru decizii strategice — jurnal executiv', + csvExtract: (d) => ({ + headers: ['Context', 'Opțiune selectată', 'Status', 'Data'], + rows: (d.data.decisions ?? []).map((dec: any) => [ + (dec.context ?? '').slice(0, 100), dec.selectedOption ?? '', dec.status ?? '', + dec.createdAt ? new Date(dec.createdAt).toLocaleDateString('ro-RO') : '', + ]), + }), + }, +]; + +export default function ExportsPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const [loading, setLoading] = useState(null); + const [lastExport, setLastExport] = useState(null); + + const { data: exportData, isLoading: fetching, refetch } = useQuery({ + queryKey: ['export-preview', tenantId], + queryFn: () => apiFetch('/v1/export/data', { tenantId }), + enabled: Boolean(tenantId), staleTime: 300_000, + }); + + const handleExport = async (key: string) => { + setLoading(key); + try { + const data = exportData ?? await refetch().then((r) => r.data!); + if (!data) return; + + if (key === 'full') { + downloadJson(data, `ceo-os-export-${TODAY}.json`); + } else { + const set = EXPORT_SETS.find((s) => s.key === key); + if (!set?.csvExtract) return; + const { headers, rows } = set.csvExtract(data); + downloadCsv([headers, ...rows], `ceo-os-${key}-${TODAY}.csv`); + } + setLastExport(key); + } finally { + setLoading(null); + } + }; + + const counts = exportData?.counts ?? {}; + + return ( +
+
+

Exporturi

+

+ Descarcă datele platformei CEO OS. Exporturile sunt generate în timp real. +

+
+ + {/* Entity counts */} + {exportData && ( +
+

Rezumat date

+
+ {Object.entries(counts).map(([k, v]) => ( +
+ {k} + {v} +
+ ))} +
+
+ )} + + {/* Export cards */} +
+ {EXPORT_SETS.map(({ key, label, icon, desc }) => { + const isLoading = loading === key; + const isDone = lastExport === key; + return ( +
+ {icon} +
+

{label}

+

{desc}

+
+ +
+ ); + })} +
+ +
+

+ ⚖ Conformitate GDPR: Exportul complet JSON respectă dreptul la portabilitate (Art. 20 GDPR). + Datele sunt generate în timp real și nu sunt stocate pe server. + Exporturile CSV sunt compatibile cu Excel (UTF-8 BOM inclus). +

+
+
+ ); +}