diff --git a/src/app/dashboard/trust/contributions/page.tsx b/src/app/dashboard/trust/contributions/page.tsx new file mode 100644 index 0000000..cdc15c6 --- /dev/null +++ b/src/app/dashboard/trust/contributions/page.tsx @@ -0,0 +1,178 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import Link from 'next/link'; +import { apiFetch } from '../../../lib/api'; +import { useSession } from '../../../components/session-provider'; + +interface Observation { id: string; metric: string; value: string; subjectType: string; source: string | null; confidence: number | null; createdAt: string; observedAt: string | null; } + +const CONTRIB_METRICS = [ + 'open-source', 'opensource', 'github', 'contributie', 'contribution', + 'publicatie', 'publication', 'articol', 'article', 'talk', 'prezentare', + 'voluntariat', 'volunteer', 'mentoring', 'mentorship', 'community', +]; + +const CONTRIB_CATEGORIES = [ + { id: 'opensource', label: 'Open Source', icon: '💻', keywords: ['github', 'open-source', 'opensource', 'contributie', 'contribution'] }, + { id: 'content', label: 'Conținut & Publicații', icon: '✍️', keywords: ['publicatie', 'publication', 'articol', 'article', 'blog', 'newsletter'] }, + { id: 'speaking', label: 'Speaking & Prezentări', icon: '🎤', keywords: ['talk', 'prezentare', 'conferinta', 'workshop', 'webinar'] }, + { id: 'mentoring', label: 'Mentoring', icon: '🧭', keywords: ['mentoring', 'mentorship', 'coaching'] }, + { id: 'volunteer', label: 'Voluntariat', icon: '🤲', keywords: ['voluntariat', 'volunteer', 'pro-bono'] }, +]; + +export default function TrustContributionsPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const qc = useQueryClient(); + const [showAdd, setShowAdd] = useState(false); + const [form, setForm] = useState({ metric: 'contributie', value: '', source: '', confidence: '1' }); + + const { data: observations = [], isLoading } = useQuery({ + queryKey: ['tc-obs', tenantId], + queryFn: () => apiFetch('/v1/observations', { tenantId }), + enabled: Boolean(tenantId), staleTime: 60_000, + }); + + const contributions = useMemo(() => + observations + .filter((o) => CONTRIB_METRICS.some((k) => o.metric.toLowerCase().includes(k))) + .sort((a, b) => new Date(b.observedAt ?? b.createdAt).getTime() - new Date(a.observedAt ?? a.createdAt).getTime()), + [observations]); + + const byCategory = useMemo(() => + CONTRIB_CATEGORIES.map((cat) => ({ + ...cat, + items: contributions.filter((c) => + cat.keywords.some((k) => c.metric.toLowerCase().includes(k)) + ), + })).filter((cat) => cat.items.length > 0), + [contributions]); + + const uncategorized = useMemo(() => + contributions.filter((c) => + !CONTRIB_CATEGORIES.some((cat) => + cat.keywords.some((k) => c.metric.toLowerCase().includes(k)) + ) + ), + [contributions]); + + const addMut = useMutation({ + mutationFn: () => apiFetch('/v1/observations', { tenantId, method: 'POST', body: { + ...form, confidence: parseFloat(form.confidence), + subjectType: 'contribution', observedAt: new Date().toISOString(), + }}), + onSuccess: () => { qc.invalidateQueries({ queryKey: ['tc-obs', tenantId] }); setShowAdd(false); setForm({ metric: 'contributie', value: '', source: '', confidence: '1' }); }, + }); + + return ( +
+
+
+ +

Contribuții & Impact

+

+ {isLoading ? 'Se încarcă…' : `${contributions.length} contribuții documentate`} +

+
+ +
+ + {showAdd && ( +
+

Contribuție nouă

+
+ + setForm((p) => ({ ...p, source: e.target.value }))} + className="rounded-lg border bg-background px-3 py-2 text-sm text-ink font-mono text-xs focus:outline-none focus:ring-2 focus:ring-ring" /> + setForm((p) => ({ ...p, value: e.target.value }))} + className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring sm:col-span-2" /> +
+
+ + +
+
+ )} + + {/* Summary chips */} +
+ {CONTRIB_CATEGORIES.map((cat) => { + const count = byCategory.find((b) => b.id === cat.id)?.items.length ?? 0; + return ( +
0 ? 'bg-primary/10 text-primary' : 'bg-muted text-ink-faint'}`}> + {cat.icon} + {cat.label} + ({count}) +
+ ); + })} +
+ + {isLoading ? ( +
Se încarcă…
+ ) : contributions.length === 0 ? ( +
+

🤲

+

Nicio contribuție documentată. Adaugă articole, PR-uri, talk-uri, voluntariat.

+
+ ) : ( +
+ {byCategory.map((cat) => ( +
+

+ {cat.icon}{cat.label} ({cat.items.length}) +

+
+ {cat.items.map((c) => ( +
+
+

{c.value}

+ {c.source && ( + + {c.source} + + )} +
+

+ {new Date(c.observedAt ?? c.createdAt).toLocaleDateString('ro-RO', { month: 'short', year: 'numeric' })} +

+
+ ))} +
+
+ ))} + {uncategorized.length > 0 && ( +
+

Alte contribuții ({uncategorized.length})

+
+ {uncategorized.map((c) => ( +
+

{c.value}

+

{c.metric}

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