feat: enhance JiraDashboardPage with new components and improved UI

- Integrated `PeriodSelector`, `SkeletonGrid`, and `MetricsGrid` for better data visualization and user interaction.
- Replaced legacy period selection and error display with new components for a cleaner UI.
- Updated `UIShowcaseClient` to demonstrate new Jira dashboard components, enhancing showcase functionality.
This commit is contained in:
Julien Froidefond
2025-09-29 16:47:35 +02:00
parent 6c0c353a4e
commit c1a14f9196
6 changed files with 304 additions and 101 deletions

View File

@@ -9,6 +9,9 @@ import { Header } from '@/components/ui/Header';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { PeriodSelector, SkeletonGrid, MetricsGrid } from '@/components/ui';
import { AlertBanner } from '@/components/ui/AlertBanner';
import { Tabs } from '@/components/ui/Tabs';
import { VelocityChart } from '@/components/jira/VelocityChart';
import { TeamDistributionChart } from '@/components/jira/TeamDistributionChart';
import { CycleTimeChart } from '@/components/jira/CycleTimeChart';
@@ -197,26 +200,16 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<div className="flex items-center gap-3">
{/* Sélecteur de période */}
<div className="flex bg-[var(--card)] border border-[var(--border)] rounded-lg p-1">
{[
<PeriodSelector
options={[
{ value: '7d', label: '7j' },
{ value: '30d', label: '30j' },
{ value: '3m', label: '3m' },
{ value: 'current', label: 'Sprint' }
].map((period: { value: string; label: string }) => (
<button
key={period.value}
onClick={() => setSelectedPeriod(period.value as PeriodFilter)}
className={`px-3 py-1 text-sm rounded transition-all ${
selectedPeriod === period.value
? 'bg-[var(--primary)] text-[var(--primary-foreground)]'
: 'text-[var(--muted-foreground)] hover:text-[var(--foreground)]'
}`}
>
{period.label}
</button>
))}
</div>
]}
selectedValue={selectedPeriod}
onValueChange={(value) => setSelectedPeriod(value as PeriodFilter)}
/>
<div className="flex items-center gap-2">
{analytics && (
@@ -260,40 +253,27 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{/* Contenu principal */}
{error && (
<Card className="mb-6 border-red-500/20 bg-red-500/10">
<CardContent className="p-4">
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span></span>
<span>{error}</span>
</div>
</CardContent>
</Card>
<AlertBanner
title="Erreur"
items={[{ id: 'error', title: error }]}
icon="❌"
variant="error"
className="mb-6"
/>
)}
{exportError && (
<Card className="mb-6 border-orange-500/20 bg-orange-500/10">
<CardContent className="p-4">
<div className="flex items-center gap-2" style={{ color: 'var(--accent)' }}>
<span></span>
<span>Erreur d&apos;export: {exportError}</span>
</div>
</CardContent>
</Card>
<AlertBanner
title="Erreur d'export"
items={[{ id: 'export-error', title: exportError }]}
icon="⚠️"
variant="warning"
className="mb-6"
/>
)}
{isLoading && !analytics && (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Skeleton loading */}
{[1, 2, 3, 4, 5, 6].map(i => (
<Card key={i} className="animate-pulse">
<CardContent className="p-6">
<div className="h-4 bg-[var(--muted)] rounded mb-4"></div>
<div className="h-8 bg-[var(--muted)] rounded mb-2"></div>
<div className="h-4 bg-[var(--muted)] rounded w-2/3"></div>
</CardContent>
</Card>
))}
</div>
<SkeletonGrid count={6} />
)}
{analytics && (
@@ -313,40 +293,30 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
</Badge>
)}
</h2>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<div className="text-center">
<div className="text-xl font-bold text-[var(--primary)]">
{analytics.project.totalIssues}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Tickets
</div>
</div>
<div className="text-center">
<div className="text-xl font-bold text-blue-500">
{analytics.teamMetrics.totalAssignees}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Équipe
</div>
</div>
<div className="text-center">
<div className="text-xl font-bold text-green-500">
{analytics.teamMetrics.activeAssignees}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Actifs
</div>
</div>
<div className="text-center">
<div className="text-xl font-bold text-orange-500">
{analytics.velocityMetrics.currentSprintPoints}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Points
</div>
</div>
</div>
<MetricsGrid
metrics={[
{
title: 'Tickets',
value: analytics.project.totalIssues,
color: 'primary'
},
{
title: 'Équipe',
value: analytics.teamMetrics.totalAssignees,
color: 'default'
},
{
title: 'Actifs',
value: analytics.teamMetrics.activeAssignees,
color: 'success'
},
{
title: 'Points',
value: analytics.velocityMetrics.currentSprintPoints,
color: 'warning'
}
]}
/>
</div>
</CardHeader>
</Card>
@@ -363,28 +333,16 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
<AnomalyDetectionPanel />
{/* Onglets de navigation */}
<div className="border-b border-[var(--border)]">
<nav className="flex space-x-8">
{[
{ id: 'overview', label: '📊 Vue d\'ensemble' },
{ id: 'velocity', label: '🚀 Vélocité & Sprints' },
{ id: 'analytics', label: '📈 Analytics avancées' },
{ id: 'quality', label: '🎯 Qualité & Collaboration' }
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as 'overview' | 'velocity' | 'analytics' | 'quality')}
className={`py-3 px-1 border-b-2 font-medium text-sm transition-colors ${
activeTab === tab.id
? 'border-[var(--primary)] text-[var(--primary)]'
: 'border-transparent text-[var(--muted-foreground)] hover:text-[var(--foreground)] hover:border-[var(--border)]'
}`}
>
{tab.label}
</button>
))}
</nav>
</div>
<Tabs
items={[
{ id: 'overview', label: '📊 Vue d\'ensemble' },
{ id: 'velocity', label: '🚀 Vélocité & Sprints' },
{ id: 'analytics', label: '📈 Analytics avancées' },
{ id: 'quality', label: '🎯 Qualité & Collaboration' }
]}
activeTab={activeTab}
onTabChange={(tabId) => setActiveTab(tabId as 'overview' | 'velocity' | 'analytics' | 'quality')}
/>
{/* Contenu des onglets */}
{activeTab === 'overview' && (