Files
towercontrol/components/dashboard/VelocityMetrics.tsx
Julien Froidefond fded7d0078 feat: add weekly summary features and components
- Introduced `CategoryBreakdown`, `JiraWeeklyMetrics`, `PeriodSelector`, and `VelocityMetrics` components to enhance the weekly summary dashboard.
- Updated `WeeklySummaryClient` to manage period selection and PDF export functionality.
- Enhanced `WeeklySummaryService` to support period comparisons and activity categorization.
- Added new API route for fetching weekly summary data based on selected period.
- Updated `package.json` and `package-lock.json` to include `jspdf` and related types for PDF generation.
- Marked several tasks as complete in `TODO.md` to reflect progress on summary features.
2025-09-19 12:28:11 +02:00

169 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import type { VelocityMetrics } from '@/services/weekly-summary';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
interface VelocityMetricsProps {
velocity: VelocityMetrics;
}
export function VelocityMetrics({ velocity }: VelocityMetricsProps) {
const getTrendIcon = (trend: number) => {
if (trend > 10) return '📈';
if (trend < -10) return '📉';
return '➡️';
};
const getTrendColor = (trend: number) => {
if (trend > 10) return 'text-[var(--success)]';
if (trend < -10) return 'text-[var(--destructive)]';
return 'text-[var(--muted-foreground)]';
};
const formatTrend = (trend: number) => {
const sign = trend > 0 ? '+' : '';
return `${sign}${trend.toFixed(1)}%`;
};
const maxActivities = Math.max(...velocity.weeklyData.map(w => w.totalActivities));
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold"> Métriques de vélocité</h3>
<p className="text-sm text-[var(--muted-foreground)]">
Performance sur les 4 dernières semaines
</p>
</CardHeader>
<CardContent className="space-y-6">
{/* Métriques principales */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--primary)]/50 transition-colors text-center">
<div className="text-2xl font-bold text-[var(--primary)]">
{velocity.currentWeekTasks}
</div>
<div className="text-sm text-[var(--muted-foreground)]">Tâches cette semaine</div>
</div>
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--muted-foreground)]/50 transition-colors text-center">
<div className="text-2xl font-bold text-[var(--muted-foreground)]">
{velocity.previousWeekTasks}
</div>
<div className="text-sm text-[var(--muted-foreground)]">Semaine précédente</div>
</div>
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--accent)]/50 transition-colors text-center">
<div className="text-2xl font-bold text-[var(--accent)]">
{velocity.fourWeekAverage.toFixed(1)}
</div>
<div className="text-sm text-[var(--muted-foreground)]">Moyenne 4 semaines</div>
</div>
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--success)]/50 transition-colors text-center">
<div className={`text-2xl font-bold ${getTrendColor(velocity.weeklyTrend)}`}>
{getTrendIcon(velocity.weeklyTrend)} {formatTrend(velocity.weeklyTrend)}
</div>
<div className="text-sm text-[var(--muted-foreground)]">Tendance</div>
</div>
</div>
{/* Graphique de tendance simple */}
<div>
<h4 className="font-medium mb-3">📊 Tendance sur 4 semaines</h4>
<div className="grid grid-cols-4 gap-2">
{velocity.weeklyData.map((week, index) => {
const height = maxActivities > 0 ? (week.totalActivities / maxActivities) * 100 : 0;
const weekLabel = `S${index + 1}`;
return (
<div key={index} className="text-center">
<div className="h-24 flex items-end justify-center mb-2">
<div
className="bg-[var(--primary)] w-8 rounded-t transition-all hover:bg-[var(--primary)]/80"
style={{ height: `${Math.max(height, 5)}%` }}
title={`${week.totalActivities} activités complétées`}
/>
</div>
<div className="text-xs font-medium">{weekLabel}</div>
<div className="text-xs text-[var(--muted-foreground)]">
{week.totalActivities}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
{week.weekStart.toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short'
})}
</div>
</div>
);
})}
</div>
</div>
{/* Détails par semaine */}
<div>
<h4 className="font-medium mb-3">📈 Détails par semaine</h4>
<div className="space-y-2">
{velocity.weeklyData.map((week, index) => {
const isCurrentWeek = index === velocity.weeklyData.length - 1;
return (
<div
key={index}
className={`flex items-center justify-between p-3 rounded-lg border transition-colors ${
isCurrentWeek ? 'bg-[var(--primary)]/10 border-[var(--primary)]/30' : 'bg-[var(--card)] border-[var(--border)] hover:border-[var(--border)]/80'
}`}
>
<div className="flex items-center gap-3">
<span className="text-sm font-medium">
Semaine du {week.weekStart.toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short'
})}
</span>
{isCurrentWeek && (
<Badge className="bg-[var(--primary)]/20 text-[var(--primary)]">Actuelle</Badge>
)}
</div>
<div className="flex items-center gap-4 text-sm">
<span className="text-[var(--success)]">
{week.completedTasks} tâches
</span>
<span className="text-[var(--primary)]">
{week.completedCheckboxes} daily
</span>
<span className="font-medium text-[var(--foreground)]">
Total: {week.totalActivities}
</span>
</div>
</div>
);
})}
</div>
</div>
{/* Insights */}
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)]">
<h4 className="font-medium mb-2">💡 Insights</h4>
<div className="text-sm text-[var(--muted-foreground)] space-y-1">
{velocity.weeklyTrend > 10 && (
<p>🚀 Excellente progression ! Vous êtes {velocity.weeklyTrend.toFixed(1)}% plus productif cette semaine.</p>
)}
{velocity.weeklyTrend < -10 && (
<p> Baisse d&apos;activité de {Math.abs(velocity.weeklyTrend).toFixed(1)}%. Peut-être temps de revoir votre planning ?</p>
)}
{Math.abs(velocity.weeklyTrend) <= 10 && (
<p> Rythme stable. Vous maintenez une productivité constante.</p>
)}
<p>
📊 Votre moyenne sur 4 semaines est de {velocity.fourWeekAverage.toFixed(1)} activités par semaine.
</p>
</div>
</div>
</CardContent>
</Card>
);
}