- 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.
147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
'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'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'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'oubliez pas les pauses et la collaboration !
|
||
</p>
|
||
);
|
||
})()}
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|