feat: metrics on Manager page
This commit is contained in:
245
components/dashboard/MetricsTab.tsx
Normal file
245
components/dashboard/MetricsTab.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useWeeklyMetrics, useVelocityTrends } from '@/hooks/use-metrics';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { DailyStatusChart } from './charts/DailyStatusChart';
|
||||
import { CompletionRateChart } from './charts/CompletionRateChart';
|
||||
import { StatusDistributionChart } from './charts/StatusDistributionChart';
|
||||
import { PriorityBreakdownChart } from './charts/PriorityBreakdownChart';
|
||||
import { VelocityTrendChart } from './charts/VelocityTrendChart';
|
||||
import { WeeklyActivityHeatmap } from './charts/WeeklyActivityHeatmap';
|
||||
import { ProductivityInsights } from './charts/ProductivityInsights';
|
||||
import { format } from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
|
||||
interface MetricsTabProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function MetricsTab({ className }: MetricsTabProps) {
|
||||
const [selectedDate] = useState<Date>(new Date());
|
||||
const [weeksBack, setWeeksBack] = useState(4);
|
||||
|
||||
const { metrics, loading: metricsLoading, error: metricsError, refetch: refetchMetrics } = useWeeklyMetrics(selectedDate);
|
||||
const { trends, loading: trendsLoading, error: trendsError, refetch: refetchTrends } = useVelocityTrends(weeksBack);
|
||||
|
||||
const handleRefresh = () => {
|
||||
refetchMetrics();
|
||||
refetchTrends();
|
||||
};
|
||||
|
||||
const formatPeriod = () => {
|
||||
if (!metrics) return '';
|
||||
return `Semaine du ${format(metrics.period.start, 'dd MMM', { locale: fr })} au ${format(metrics.period.end, 'dd MMM yyyy', { locale: fr })}`;
|
||||
};
|
||||
|
||||
const getTrendIcon = (trend: string) => {
|
||||
switch (trend) {
|
||||
case 'improving': return '📈';
|
||||
case 'declining': return '📉';
|
||||
case 'stable': return '➡️';
|
||||
default: return '📊';
|
||||
}
|
||||
};
|
||||
|
||||
const getPatternIcon = (pattern: string) => {
|
||||
switch (pattern) {
|
||||
case 'consistent': return '🎯';
|
||||
case 'variable': return '📊';
|
||||
case 'weekend-heavy': return '📅';
|
||||
default: return '📋';
|
||||
}
|
||||
};
|
||||
|
||||
if (metricsError || trendsError) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Card>
|
||||
<CardContent className="p-6 text-center">
|
||||
<p className="text-red-500 mb-4">
|
||||
❌ Erreur lors du chargement des métriques
|
||||
</p>
|
||||
<p className="text-sm text-[var(--muted-foreground)] mb-4">
|
||||
{metricsError || trendsError}
|
||||
</p>
|
||||
<Button onClick={handleRefresh} variant="secondary" size="sm">
|
||||
🔄 Réessayer
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* Header avec période et contrôles */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-[var(--foreground)]">📊 Métriques & Analytics</h2>
|
||||
<p className="text-[var(--muted-foreground)]">{formatPeriod()}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={handleRefresh}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={metricsLoading || trendsLoading}
|
||||
>
|
||||
🔄 Actualiser
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{metricsLoading || trendsLoading ? (
|
||||
<Card>
|
||||
<CardContent className="p-6 text-center">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-4 bg-[var(--border)] rounded w-1/4 mx-auto mb-4"></div>
|
||||
<div className="h-32 bg-[var(--border)] rounded"></div>
|
||||
</div>
|
||||
<p className="text-[var(--muted-foreground)] mt-4">Chargement des métriques...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : metrics ? (
|
||||
<div className="space-y-6">
|
||||
{/* Vue d'ensemble rapide */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">🎯 Vue d'ensemble</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
|
||||
<div className="text-center p-4 bg-green-50 dark:bg-green-950/20 rounded-lg">
|
||||
<div className="text-2xl font-bold text-green-600">
|
||||
{metrics.summary.totalTasksCompleted}
|
||||
</div>
|
||||
<div className="text-sm text-green-600">Terminées</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg">
|
||||
<div className="text-2xl font-bold text-blue-600">
|
||||
{metrics.summary.totalTasksCreated}
|
||||
</div>
|
||||
<div className="text-sm text-blue-600">Créées</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-purple-50 dark:bg-purple-950/20 rounded-lg">
|
||||
<div className="text-2xl font-bold text-purple-600">
|
||||
{metrics.summary.averageCompletionRate.toFixed(1)}%
|
||||
</div>
|
||||
<div className="text-sm text-purple-600">Taux moyen</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-orange-50 dark:bg-orange-950/20 rounded-lg">
|
||||
<div className="text-2xl font-bold text-orange-600">
|
||||
{getTrendIcon(metrics.summary.trendsAnalysis.completionTrend)}
|
||||
</div>
|
||||
<div className="text-sm text-orange-600 capitalize">
|
||||
{metrics.summary.trendsAnalysis.completionTrend}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-4 bg-gray-50 dark:bg-gray-950/20 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-600">
|
||||
{getPatternIcon(metrics.summary.trendsAnalysis.productivityPattern)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{metrics.summary.trendsAnalysis.productivityPattern === 'consistent' ? 'Régulier' :
|
||||
metrics.summary.trendsAnalysis.productivityPattern === 'variable' ? 'Variable' : 'Weekend+'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Graphiques principaux */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">📈 Évolution quotidienne des statuts</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<DailyStatusChart data={metrics.dailyBreakdown} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">🎯 Taux de completion quotidien</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CompletionRateChart data={metrics.dailyBreakdown} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Distribution et priorités */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">🍰 Répartition des statuts</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<StatusDistributionChart data={metrics.statusDistribution} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">⚡ Performance par priorité</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<PriorityBreakdownChart data={metrics.priorityBreakdown} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">🔥 Heatmap d'activité</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<WeeklyActivityHeatmap data={metrics.dailyBreakdown} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Tendances de vélocité */}
|
||||
{trends.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">🚀 Tendances de vélocité</h3>
|
||||
<select
|
||||
value={weeksBack}
|
||||
onChange={(e) => setWeeksBack(parseInt(e.target.value))}
|
||||
className="text-sm border border-[var(--border)] rounded px-2 py-1 bg-[var(--background)]"
|
||||
>
|
||||
<option value={4}>4 semaines</option>
|
||||
<option value={8}>8 semaines</option>
|
||||
<option value={12}>12 semaines</option>
|
||||
</select>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<VelocityTrendChart data={trends} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Analyses de productivité */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">💡 Analyses de productivité</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ProductivityInsights data={metrics.dailyBreakdown} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user