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:
155
components/dashboard/PeriodSelector.tsx
Normal file
155
components/dashboard/PeriodSelector.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
'use client';
|
||||
|
||||
import { PERIOD_OPTIONS, PeriodOption, PeriodComparison } from '@/services/weekly-summary';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
interface PeriodSelectorProps {
|
||||
currentPeriod: PeriodOption;
|
||||
onPeriodChange: (period: PeriodOption) => void;
|
||||
comparison: PeriodComparison | null;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function PeriodSelector({
|
||||
currentPeriod,
|
||||
onPeriodChange,
|
||||
comparison,
|
||||
isLoading = false
|
||||
}: PeriodSelectorProps) {
|
||||
|
||||
const formatChange = (change: number): string => {
|
||||
const sign = change > 0 ? '+' : '';
|
||||
return `${sign}${change.toFixed(1)}%`;
|
||||
};
|
||||
|
||||
const getChangeColor = (change: number): string => {
|
||||
if (change > 10) return 'text-[var(--success)]';
|
||||
if (change < -10) return 'text-[var(--destructive)]';
|
||||
return 'text-[var(--muted-foreground)]';
|
||||
};
|
||||
|
||||
const getChangeIcon = (change: number): string => {
|
||||
if (change > 10) return '📈';
|
||||
if (change < -10) return '📉';
|
||||
return '➡️';
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">📊 Sélection de période</h3>
|
||||
<div className="flex gap-2">
|
||||
{PERIOD_OPTIONS.map((option) => (
|
||||
<Button
|
||||
key={option.key}
|
||||
onClick={() => onPeriodChange(option)}
|
||||
variant={currentPeriod.key === option.key ? "primary" : "secondary"}
|
||||
size="sm"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
{comparison && (
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{/* Titre de comparaison */}
|
||||
<h4 className="font-medium">
|
||||
Comparaison avec la période précédente
|
||||
</h4>
|
||||
|
||||
{/* Métriques de comparaison */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{/* Tâches */}
|
||||
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--primary)]/50 transition-colors">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-[var(--primary)]">Tâches complétées</span>
|
||||
<span className={`text-sm font-medium ${getChangeColor(comparison.changes.tasks)}`}>
|
||||
{getChangeIcon(comparison.changes.tasks)} {formatChange(comparison.changes.tasks)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[var(--foreground)] font-medium">
|
||||
Actuelle: {comparison.currentPeriod.tasks}
|
||||
</span>
|
||||
<span className="text-[var(--muted-foreground)]">
|
||||
Précédente: {comparison.previousPeriod.tasks}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Daily items */}
|
||||
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--success)]/50 transition-colors">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-[var(--success)]">Daily items</span>
|
||||
<span className={`text-sm font-medium ${getChangeColor(comparison.changes.checkboxes)}`}>
|
||||
{getChangeIcon(comparison.changes.checkboxes)} {formatChange(comparison.changes.checkboxes)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[var(--foreground)] font-medium">
|
||||
Actuelle: {comparison.currentPeriod.checkboxes}
|
||||
</span>
|
||||
<span className="text-[var(--muted-foreground)]">
|
||||
Précédente: {comparison.previousPeriod.checkboxes}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Total */}
|
||||
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--accent)]/50 transition-colors">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-[var(--accent)]">Total activités</span>
|
||||
<span className={`text-sm font-medium ${getChangeColor(comparison.changes.total)}`}>
|
||||
{getChangeIcon(comparison.changes.total)} {formatChange(comparison.changes.total)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-[var(--foreground)] font-medium">
|
||||
Actuelle: {comparison.currentPeriod.total}
|
||||
</span>
|
||||
<span className="text-[var(--muted-foreground)]">
|
||||
Précédente: {comparison.previousPeriod.total}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Insights sur la comparaison */}
|
||||
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)]">
|
||||
<h5 className="font-medium mb-2">💡 Insights comparatifs</h5>
|
||||
<div className="text-sm text-[var(--muted-foreground)] space-y-1">
|
||||
{comparison.changes.total > 15 && (
|
||||
<p>🚀 Excellente progression ! Productivité en hausse de {formatChange(comparison.changes.total)}.</p>
|
||||
)}
|
||||
{comparison.changes.total < -15 && (
|
||||
<p>📉 Baisse d'activité de {formatChange(Math.abs(comparison.changes.total))}. Période moins chargée ?</p>
|
||||
)}
|
||||
{Math.abs(comparison.changes.total) <= 15 && (
|
||||
<p>✅ Rythme stable maintenu entre les deux périodes.</p>
|
||||
)}
|
||||
|
||||
{comparison.changes.tasks > comparison.changes.checkboxes + 10 && (
|
||||
<p>🎯 Focus accru sur les tâches importantes cette période.</p>
|
||||
)}
|
||||
{comparison.changes.checkboxes > comparison.changes.tasks + 10 && (
|
||||
<p>📝 Activité quotidienne plus intense cette période.</p>
|
||||
)}
|
||||
|
||||
<p>
|
||||
📊 Évolution globale: {comparison.currentPeriod.total} activités vs {comparison.previousPeriod.total} la période précédente.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user