diff --git a/src/app/dashboard/strategy-map/page.tsx b/src/app/dashboard/strategy-map/page.tsx new file mode 100644 index 0000000..847dcad --- /dev/null +++ b/src/app/dashboard/strategy-map/page.tsx @@ -0,0 +1,145 @@ +'use client'; + +import { useMemo } 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; progress: number | null; tags: string[]; description: string | null; } + +const PERSPECTIVES = [ + { id: 'financial', label: 'Financiar', icon: '💰', keywords: ['financiar', 'revenue', 'profit', 'cost', 'venit', 'bani'] }, + { id: 'client', label: 'Client & Piață', icon: '🎯', keywords: ['client', 'market', 'piata', 'customer', 'vanzare', 'sales'] }, + { id: 'operational', label: 'Procese', icon: '⚙️', keywords: ['proces', 'operationa', 'eficient', 'automat', 'sistem'] }, + { id: 'growth', label: 'Creștere & Inov.', icon: '🚀', keywords: ['crestere', 'inovatie', 'learn', 'develop', 'skill', 'educatie'] }, +]; + +export default function StrategyMapPage() { + const { activeTenant } = useSession(); + const tenantId = activeTenant?.tenantId ?? ''; + + const { data: goals = [], isLoading } = useQuery({ + queryKey: ['strategy-goals', tenantId], + queryFn: () => apiFetch('/v1/goals?limit=200', { tenantId }), + enabled: Boolean(tenantId), staleTime: 60_000, + }); + + const byPerspective = useMemo(() => { + return PERSPECTIVES.map((p) => { + const matched = goals.filter((g) => + p.keywords.some((k) => + g.title.toLowerCase().includes(k) || + (g.description ?? '').toLowerCase().includes(k) || + g.tags.some((t) => t.toLowerCase().includes(k)) + ) + ); + const inProgress = matched.filter((g) => g.status === 'active'); + const completed = matched.filter((g) => g.status === 'completed'); + const avgProgress = inProgress.length > 0 + ? Math.round(inProgress.reduce((s, g) => s + (g.progress ?? 0), 0) / inProgress.length) + : 0; + return { ...p, matched, inProgress, completed, avgProgress }; + }); + }, [goals]); + + const unclassified = useMemo(() => { + const classified = new Set(byPerspective.flatMap((p) => p.matched.map((g) => g.id))); + return goals.filter((g) => !classified.has(g.id) && g.status === 'active'); + }, [goals, byPerspective]); + + function statusDot(g: Goal) { + if (g.status === 'completed') return 'bg-signal-ok'; + if ((g.progress ?? 0) > 50) return 'bg-primary'; + if ((g.progress ?? 0) > 0) return 'bg-warn'; + return 'bg-muted'; + } + + return ( +
+
+
+

Strategy Map

+

+ Obiectivele tale organizate pe perspectivele Balanced Scorecard. + {isLoading ? '' : ` ${goals.filter((g) => g.status === 'active').length} obiective active`} +

+
+ + + Adaugă obiectiv + +
+ +
+ {byPerspective.map((p) => ( +
+
+
+ {p.icon} +

{p.label}

+
+
+

{p.matched.length}

+

obiective

+
+
+ + {p.inProgress.length > 0 && ( +
+ {p.inProgress.slice(0, 4).map((g) => ( +
+ +

{g.title}

+ {g.progress !== null && ( + {g.progress}% + )} +
+ ))} + {p.inProgress.length > 4 && ( +

+{p.inProgress.length - 4} mai multe

+ )} +
+ )} + + {p.inProgress.length > 0 && ( +
+
+ Progress mediu{p.avgProgress}% +
+
+
+
+
+ )} + + {p.matched.length === 0 && ( +

Niciun obiectiv clasificat în această perspectivă.

+ )} + + {p.completed.length > 0 && ( +

✓ {p.completed.length} finalizate

+ )} +
+ ))} +
+ + {/* Unclassified */} + {unclassified.length > 0 && ( +
+

Obiective neclasificate ({unclassified.length})

+

Adaugă cuvinte cheie în titlu sau tags pentru a le clasifica automat.

+
+ {unclassified.slice(0, 5).map((g) => ( +

• {g.title}

+ ))} +
+
+ )} + +
+ Toate obiectivele → + Raport personalizat → +
+
+ ); +}