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.
This commit is contained in:
Julien Froidefond
2025-09-19 12:28:11 +02:00
parent f9c0035c82
commit fded7d0078
14 changed files with 2028 additions and 111 deletions

View File

@@ -0,0 +1,168 @@
'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>
);
}