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,146 @@
'use client';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
interface CategoryData {
count: number;
percentage: number;
color: string;
icon: string;
}
interface CategoryBreakdownProps {
categoryData: { [categoryName: string]: CategoryData };
totalActivities: number;
}
export function CategoryBreakdown({ categoryData, totalActivities }: CategoryBreakdownProps) {
const categories = Object.entries(categoryData)
.filter(([, data]) => data.count > 0)
.sort((a, b) => b[1].count - a[1].count);
if (categories.length === 0) {
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">📊 Répartition par catégorie</h3>
</CardHeader>
<CardContent>
<p className="text-center text-[var(--muted-foreground)]">
Aucune activité à catégoriser
</p>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<h3 className="text-lg font-semibold">📊 Répartition par catégorie</h3>
<p className="text-sm text-[var(--muted-foreground)]">
Analyse automatique de vos {totalActivities} activités
</p>
</CardHeader>
<CardContent className="space-y-6">
{/* Légende des catégories */}
<div className="flex flex-wrap gap-3 justify-center">
{categories.map(([categoryName, data]) => (
<div
key={categoryName}
className="flex items-center gap-2 bg-[var(--card)] border border-[var(--border)] rounded-lg px-3 py-2 hover:border-[var(--primary)]/50 transition-colors"
>
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: data.color }}
/>
<span className="text-sm font-medium text-[var(--foreground)]">
{data.icon} {categoryName}
</span>
<Badge className="bg-[var(--primary)]/10 text-[var(--primary)] text-xs">
{data.count}
</Badge>
</div>
))}
</div>
{/* Barres de progression */}
<div className="space-y-3">
{categories.map(([categoryName, data]) => (
<div key={categoryName} className="space-y-1">
<div className="flex items-center justify-between text-sm">
<span className="flex items-center gap-2">
<span>{data.icon}</span>
<span className="font-medium">{categoryName}</span>
</span>
<span className="text-[var(--muted-foreground)]">
{data.count} ({data.percentage.toFixed(1)}%)
</span>
</div>
<div className="w-full bg-[var(--border)] rounded-full h-2">
<div
className="h-2 rounded-full transition-all duration-500"
style={{
backgroundColor: data.color,
width: `${data.percentage}%`
}}
/>
</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">
{categories.length > 0 && (
<>
<p>
🏆 <strong>{categories[0][0]}</strong> est votre activité principale
({categories[0][1].percentage.toFixed(1)}% de votre temps).
</p>
{categories.length > 1 && (
<p>
📈 Vous avez une bonne diversité avec {categories.length} catégories d&apos;activités.
</p>
)}
{/* Suggestions basées sur la répartition */}
{categories.some(([, data]) => data.percentage > 70) && (
<p>
Forte concentration sur une seule catégorie.
Pensez à diversifier vos activités pour un meilleur équilibre.
</p>
)}
{(() => {
const learningCategory = categories.find(([name]) => name === 'Learning');
return learningCategory && learningCategory[1].percentage > 0 && (
<p>
🎓 Excellent ! Vous consacrez du temps à l&apos;apprentissage
({learningCategory[1].percentage.toFixed(1)}%).
</p>
);
})()}
{(() => {
const devCategory = categories.find(([name]) => name === 'Dev');
return devCategory && devCategory[1].percentage > 50 && (
<p>
💻 Focus développement intense. N&apos;oubliez pas les pauses et la collaboration !
</p>
);
})()}
</>
)}
</div>
</div>
</CardContent>
</Card>
);
}