246 lines
9.3 KiB
TypeScript
246 lines
9.3 KiB
TypeScript
'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>
|
|
);
|
|
}
|