import { useEffect, useState, useCallback } from 'react'; import { Link } from 'react-router-dom'; import { getDashboardStats, getRecentClassifications } from '../services/api'; import type { DashboardStats, ClassificationResult } from '../types'; // Extended type to include stale indicator from API interface DashboardStatsWithMeta extends DashboardStats { stale?: boolean; error?: string; } export default function Dashboard() { const [stats, setStats] = useState(null); const [recentClassifications, setRecentClassifications] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const fetchData = useCallback(async (forceRefresh: boolean = false) => { if (forceRefresh) { setRefreshing(true); } else { setLoading(true); } setError(null); try { const [statsData, recentData] = await Promise.all([ getDashboardStats(forceRefresh), getRecentClassifications(10), ]); setStats(statsData as DashboardStatsWithMeta); setRecentClassifications(recentData); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load dashboard'); } finally { setLoading(false); setRefreshing(false); } }, []); useEffect(() => { fetchData(false); }, [fetchData]); const handleRefresh = () => { fetchData(true); }; if (loading) { return (
); } if (error) { return (
{error}
); } const progressPercentage = stats ? Math.round((stats.classifiedCount / stats.totalApplications) * 100) : 0; return (
{/* Page header */}

Dashboard

Overzicht van de ZiRA classificatie voortgang {stats?.stale && ( (gecachte data - API timeout) )}

Start classificeren
{/* Stats cards */}
Totaal applicaties
{stats?.totalApplications || 0}
Geclassificeerd
{stats?.classifiedCount || 0}
Nog te classificeren
{Math.max(0, stats?.unclassifiedCount || 0)}
Voortgang
{progressPercentage}%
{/* Progress bar */}

Classificatie voortgang

ApplicationFunction ingevuld {stats?.classifiedCount || 0} / {stats?.totalApplications || 0}
{/* Two column layout */}
{/* Status distribution */}

Verdeling per status

{stats?.byStatus && Object.entries(stats.byStatus) .sort((a, b) => { // Sort alphabetically, but put "Undefined" at the end if (a[0] === 'Undefined') return 1; if (b[0] === 'Undefined') return -1; return a[0].localeCompare(b[0], 'nl', { sensitivity: 'base' }); }) .map(([status, count]) => (
{status}
{count}
))}
{/* Governance model distribution */}

Verdeling per regiemodel

{stats?.byGovernanceModel && Object.entries(stats.byGovernanceModel) .sort((a, b) => { // Sort alphabetically, but put "Niet ingesteld" at the end if (a[0] === 'Niet ingesteld') return 1; if (b[0] === 'Niet ingesteld') return -1; return a[0].localeCompare(b[0], 'nl', { sensitivity: 'base' }); }) .map(([model, count]) => (
{model}
{count}
))} {(!stats?.byGovernanceModel || Object.keys(stats.byGovernanceModel).length === 0) && (

Geen data beschikbaar

)}
{/* Recent classifications */}

Recente classificaties

{recentClassifications.length === 0 ? (
Nog geen classificaties uitgevoerd
) : ( recentClassifications.map((item, index) => (
{item.applicationName}
{item.changes.applicationFunctions && item.changes.applicationFunctions.to.length > 0 && ( ApplicationFunctions: {item.changes.applicationFunctions.to.map((f) => f.name).join(', ')} )}
{item.source === 'AI_ACCEPTED' ? 'AI Geaccepteerd' : item.source === 'AI_MODIFIED' ? 'AI Aangepast' : 'Handmatig'} {new Date(item.timestamp).toLocaleString('nl-NL')}
)) )}
); }