diff --git a/src/app/dashboard/opportunities/fit/page.tsx b/src/app/dashboard/opportunities/fit/page.tsx new file mode 100644 index 0000000..e0446e5 --- /dev/null +++ b/src/app/dashboard/opportunities/fit/page.tsx @@ -0,0 +1,129 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import Link from 'next/link'; +import { apiFetch } from '../../../../lib/api'; +import { useSession } from '../../../../components/session-provider'; + +interface Goal { id: string; title: string; status: string; tags: string[]; } +interface Observation { id: string; metric: string; value: string; source: string | null; } +interface Task { id: string; title: string; status: string; tags: string[]; } + +const CRITERIA_PRESETS = [ + { id: 'startup', label: 'Startup Accelerator', criteria: ['pitch deck', 'mvp', 'revenue', 'team', 'market'] }, + { id: 'grant', label: 'Grant / Finanțare EU', criteria: ['business plan', 'impact', 'budget', 'timeline', 'co-finantare'] }, + { id: 'job', label: 'Rol Tech / CTO', criteria: ['leadership', 'technical depth', 'product sense', 'english', 'remote'] }, + { id: 'speaking', label: 'Speaker / Conferință', criteria: ['bio', 'abstract', 'past talks', 'slides', 'availability'] }, +]; + +export default function EligibilityFitPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + const [preset, setPreset] = useState(CRITERIA_PRESETS[0]); + const [custom, setCustom] = useState(''); + const [checks, setChecks] = useState>({}); + + const { data: goals = [] } = useQuery({ queryKey: ['fit-goals', tenantId], queryFn: () => apiFetch('/v1/goals?limit=200', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: observations = [] } = useQuery({ queryKey: ['fit-obs', tenantId], queryFn: () => apiFetch('/v1/observations', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + const { data: tasks = [] } = useQuery({ queryKey: ['fit-tasks', tenantId], queryFn: () => apiFetch('/v1/tasks?limit=500', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 }); + + const allCriteria = useMemo(() => { + const extra = custom.split(',').map((s) => s.trim()).filter(Boolean); + return [...preset.criteria, ...extra]; + }, [preset.criteria, custom]); + + const autoMatch = useMemo(() => { + const matches: Record = {}; + for (const crit of allCriteria) { + const lc = crit.toLowerCase(); + const fromObs = observations.some((o) => o.metric.toLowerCase().includes(lc) || o.value.toLowerCase().includes(lc) || (o.source ?? '').toLowerCase().includes(lc)); + const fromGoal = goals.some((g) => g.title.toLowerCase().includes(lc) && g.status === 'completed'); + const fromTask = tasks.some((t) => t.title.toLowerCase().includes(lc) && t.status === 'completed'); + matches[crit] = { matched: fromObs || fromGoal || fromTask, source: fromObs ? 'observație' : fromGoal ? 'obiectiv' : fromTask ? 'task' : '' }; + } + return matches; + }, [allCriteria, observations, goals, tasks]); + + const manualCheck = (crit: string) => setChecks((p) => ({ ...p, [crit]: !p[crit] })); + + const score = useMemo(() => { + if (allCriteria.length === 0) return 0; + const met = allCriteria.filter((c) => autoMatch[c]?.matched || checks[c]).length; + return Math.round((met / allCriteria.length) * 100); + }, [allCriteria, autoMatch, checks]); + + return ( +
+
+ +

Eligibilitate & Fit

+

Analiză rapidă cât de bine te potrivești pentru o oportunitate.

+
+ + {/* Preset selector */} +
+

Tip oportunitate

+
+ {CRITERIA_PRESETS.map((p) => ( + + ))} +
+
+ + setCustom(e.target.value)} + placeholder="ex: german, 3 ani experienta, portofoliu public" + 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" /> +
+
+ + {/* Score */} +
+
+

= 70 ? 'text-signal-ok' : score >= 40 ? 'text-warn' : 'text-signal-danger'}`}>{score}%

+

fit estimat

+
+
+
+
= 70 ? 'bg-signal-ok' : score >= 40 ? 'bg-warn' : 'bg-signal-danger'}`} + style={{ width: `${score}%` }} /> +
+

+ {score >= 70 ? '✅ Profil solid — aplică!' : score >= 40 ? '⚠ Profil parțial — pregătire recomandată' : '❌ Lipsesc mai mulți indicatori cheie'} +

+
+
+ + {/* Criteria checklist */} +
+

Criterii ({allCriteria.length})

+ {allCriteria.map((crit) => { + const auto = autoMatch[crit]; + const done = auto?.matched || checks[crit]; + return ( + + ); + })} +
+ +
+

+ Potrivirea auto se bazează pe datele din CEO OS (observații, obiective, taskuri). + Bifează manual criteriile confirmate extern (portofoliu, documente, recomandări). +

+
+
+ ); +}