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:
193
components/dashboard/JiraWeeklyMetrics.tsx
Normal file
193
components/dashboard/JiraWeeklyMetrics.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
'use client';
|
||||
|
||||
import type { JiraWeeklyMetrics } from '@/services/jira-summary';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { JiraSummaryService } from '@/services/jira-summary';
|
||||
|
||||
interface JiraWeeklyMetricsProps {
|
||||
jiraMetrics: JiraWeeklyMetrics | null;
|
||||
}
|
||||
|
||||
export function JiraWeeklyMetrics({ jiraMetrics }: JiraWeeklyMetricsProps) {
|
||||
if (!jiraMetrics) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">🔗 Contexte business Jira</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-center text-[var(--muted-foreground)]">
|
||||
Configuration Jira non disponible
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (jiraMetrics.totalJiraTasks === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">🔗 Contexte business Jira</h3>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-center text-[var(--muted-foreground)]">
|
||||
Aucune tâche Jira cette semaine
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const completionRate = (jiraMetrics.completedJiraTasks / jiraMetrics.totalJiraTasks) * 100;
|
||||
const insights = JiraSummaryService.generateBusinessInsights(jiraMetrics);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold">🔗 Contexte business Jira</h3>
|
||||
<p className="text-sm text-[var(--muted-foreground)]">
|
||||
Impact business et métriques projet
|
||||
</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)]">
|
||||
{jiraMetrics.totalJiraTasks}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--muted-foreground)]">Tickets Jira</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 text-[var(--success)]">
|
||||
{completionRate.toFixed(0)}%
|
||||
</div>
|
||||
<div className="text-sm text-[var(--muted-foreground)]">Taux completion</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)]">
|
||||
{jiraMetrics.totalStoryPoints}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--muted-foreground)]">Story Points*</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)] hover:border-[var(--warning)]/50 transition-colors text-center">
|
||||
<div className="text-2xl font-bold text-[var(--warning)]">
|
||||
{jiraMetrics.projectsContributed.length}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--muted-foreground)]">Projet(s)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Projets contributés */}
|
||||
{jiraMetrics.projectsContributed.length > 0 && (
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">📂 Projets contributés</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{jiraMetrics.projectsContributed.map(project => (
|
||||
<Badge key={project} className="bg-[var(--primary)]/10 text-[var(--primary)]">
|
||||
{project}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Types de tickets */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-3">🎯 Types de tickets</h4>
|
||||
<div className="space-y-2">
|
||||
{Object.entries(jiraMetrics.ticketTypes)
|
||||
.sort(([,a], [,b]) => b - a)
|
||||
.map(([type, count]) => {
|
||||
const percentage = (count / jiraMetrics.totalJiraTasks) * 100;
|
||||
return (
|
||||
<div key={type} className="flex items-center justify-between">
|
||||
<span className="text-sm text-[var(--foreground)]">{type}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-20 bg-[var(--border)] rounded-full h-2">
|
||||
<div
|
||||
className="h-2 bg-[var(--primary)] rounded-full transition-all"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-[var(--muted-foreground)] w-8">
|
||||
{count}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Liens vers les tickets */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-3">🎫 Tickets traités</h4>
|
||||
<div className="space-y-2 max-h-40 overflow-y-auto">
|
||||
{jiraMetrics.jiraLinks.map((link) => (
|
||||
<div
|
||||
key={link.key}
|
||||
className="flex items-center justify-between p-2 rounded border hover:bg-[var(--muted)] transition-colors"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<a
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[var(--primary)] hover:underline font-medium text-sm"
|
||||
>
|
||||
{link.key}
|
||||
</a>
|
||||
<Badge
|
||||
className={`text-xs ${
|
||||
link.status === 'done'
|
||||
? 'bg-[var(--success)]/10 text-[var(--success)]'
|
||||
: 'bg-[var(--muted)]/50 text-[var(--muted-foreground)]'
|
||||
}`}
|
||||
>
|
||||
{link.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-[var(--muted-foreground)] truncate">
|
||||
{link.title}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-[var(--muted-foreground)]">
|
||||
<span>{link.type}</span>
|
||||
<span>{link.estimatedPoints}pts</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Insights business */}
|
||||
{insights.length > 0 && (
|
||||
<div className="bg-[var(--card)] p-4 rounded-lg border border-[var(--border)]">
|
||||
<h4 className="font-medium mb-2">💡 Insights business</h4>
|
||||
<div className="text-sm text-[var(--muted-foreground)] space-y-1">
|
||||
{insights.map((insight, index) => (
|
||||
<p key={index}>{insight}</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Note sur les story points */}
|
||||
<div className="text-xs text-[var(--muted-foreground)] bg-[var(--card)] border border-[var(--border)] p-2 rounded">
|
||||
<p>
|
||||
* Story Points estimés automatiquement basés sur le type de ticket
|
||||
(Epic: 8pts, Story: 3pts, Task: 2pts, Bug: 1pt)
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user