From 0a03e404698b2de4f90ccaa41f5cafb768253b6a Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 21 Sep 2025 15:55:11 +0200 Subject: [PATCH] feat: enhance metrics dashboard with new components and data handling - Introduced `MetricsOverview`, `MetricsMainCharts`, `MetricsDistributionCharts`, `MetricsVelocitySection`, and `MetricsProductivitySection` for improved metrics visualization. - Updated `MetricsTab` to integrate new components and streamline data presentation. - Added compatibility fields in `JiraTask` and `AssigneeDistribution` for better data handling. - Refactored `calculateAssigneeDistribution` to include a count for total issues. - Enhanced `JiraAnalyticsService` and `JiraAdvancedFiltersService` to support new metrics calculations. - Cleaned up unused imports and components for a more maintainable codebase. --- src/actions/jira-sprint-details.ts | 3 +- src/components/dashboard/MetricsTab.tsx | 177 +------- .../charts/MetricsDistributionCharts.tsx | 44 ++ .../dashboard/charts/MetricsMainCharts.tsx | 34 ++ .../dashboard/charts/MetricsOverview.tsx | 79 ++++ .../charts/MetricsProductivitySection.tsx | 22 + .../charts/MetricsVelocitySection.tsx | 55 +++ src/components/forms/EditTaskForm.tsx | 175 +------- src/components/forms/task/TaskBasicFields.tsx | 118 +++++ src/components/forms/task/TaskJiraInfo.tsx | 67 +++ src/components/forms/task/TaskTagsSection.tsx | 33 ++ src/components/jira/AdvancedFiltersPanel.tsx | 347 +++------------ src/components/jira/AnomalyDetectionPanel.tsx | 256 ++--------- src/components/jira/SprintDetailModal.tsx | 416 +++--------------- .../jira/anomaly/AnomalyConfigModal.tsx | 121 +++++ src/components/jira/anomaly/AnomalyItem.tsx | 69 +++ src/components/jira/anomaly/AnomalyList.tsx | 49 +++ .../jira/anomaly/AnomalySummary.tsx | 46 ++ src/components/jira/filters/FilterModal.tsx | 144 ++++++ src/components/jira/filters/FilterSection.tsx | 97 ++++ src/components/jira/filters/FilterSummary.tsx | 74 ++++ src/components/jira/sprint/SprintIssues.tsx | 180 ++++++++ src/components/jira/sprint/SprintMetrics.tsx | 214 +++++++++ src/components/jira/sprint/SprintOverview.tsx | 128 ++++++ src/components/kanban/BoardContainer.tsx | 80 +--- src/components/kanban/BoardRouter.tsx | 69 +++ src/components/kanban/KanbanHeader.tsx | 58 +++ .../settings/GeneralSettingsPageClient.tsx | 380 +++------------- .../settings/SettingsIndexPageClient.tsx | 260 +---------- .../settings/index/QuickActions.tsx | 97 ++++ src/components/settings/index/QuickStats.tsx | 73 +++ .../settings/index/SettingsNavigation.tsx | 69 +++ src/components/settings/index/SystemInfo.tsx | 79 ++++ src/components/settings/tags/TagsFilters.tsx | 61 +++ src/components/settings/tags/TagsGrid.tsx | 135 ++++++ .../settings/tags/TagsManagement.tsx | 160 +++++++ src/components/settings/tags/TagsStats.tsx | 33 ++ src/contexts/UserPreferencesContext.tsx | 31 +- src/hooks/use-metrics.ts | 4 + src/lib/types.ts | 6 + src/services/jira-advanced-filters.ts | 3 +- src/services/jira-analytics.ts | 6 +- src/services/system-info.ts | 34 +- 43 files changed, 2781 insertions(+), 1805 deletions(-) create mode 100644 src/components/dashboard/charts/MetricsDistributionCharts.tsx create mode 100644 src/components/dashboard/charts/MetricsMainCharts.tsx create mode 100644 src/components/dashboard/charts/MetricsOverview.tsx create mode 100644 src/components/dashboard/charts/MetricsProductivitySection.tsx create mode 100644 src/components/dashboard/charts/MetricsVelocitySection.tsx create mode 100644 src/components/forms/task/TaskBasicFields.tsx create mode 100644 src/components/forms/task/TaskJiraInfo.tsx create mode 100644 src/components/forms/task/TaskTagsSection.tsx create mode 100644 src/components/jira/anomaly/AnomalyConfigModal.tsx create mode 100644 src/components/jira/anomaly/AnomalyItem.tsx create mode 100644 src/components/jira/anomaly/AnomalyList.tsx create mode 100644 src/components/jira/anomaly/AnomalySummary.tsx create mode 100644 src/components/jira/filters/FilterModal.tsx create mode 100644 src/components/jira/filters/FilterSection.tsx create mode 100644 src/components/jira/filters/FilterSummary.tsx create mode 100644 src/components/jira/sprint/SprintIssues.tsx create mode 100644 src/components/jira/sprint/SprintMetrics.tsx create mode 100644 src/components/jira/sprint/SprintOverview.tsx create mode 100644 src/components/kanban/BoardRouter.tsx create mode 100644 src/components/kanban/KanbanHeader.tsx create mode 100644 src/components/settings/index/QuickActions.tsx create mode 100644 src/components/settings/index/QuickStats.tsx create mode 100644 src/components/settings/index/SettingsNavigation.tsx create mode 100644 src/components/settings/index/SystemInfo.tsx create mode 100644 src/components/settings/tags/TagsFilters.tsx create mode 100644 src/components/settings/tags/TagsGrid.tsx create mode 100644 src/components/settings/tags/TagsManagement.tsx create mode 100644 src/components/settings/tags/TagsStats.tsx diff --git a/src/actions/jira-sprint-details.ts b/src/actions/jira-sprint-details.ts index 0997442..3d57e3c 100644 --- a/src/actions/jira-sprint-details.ts +++ b/src/actions/jira-sprint-details.ts @@ -170,7 +170,8 @@ function calculateAssigneeDistribution(issues: JiraTask[]): AssigneeDistribution totalIssues: stats.total, completedIssues: stats.completed, inProgressIssues: stats.inProgress, - percentage: issues.length > 0 ? (stats.total / issues.length) * 100 : 0 + percentage: issues.length > 0 ? (stats.total / issues.length) * 100 : 0, + count: stats.total // Ajout pour compatibilité })).sort((a, b) => b.totalIssues - a.totalIssues); } diff --git a/src/components/dashboard/MetricsTab.tsx b/src/components/dashboard/MetricsTab.tsx index a40b9c1..709fa8b 100644 --- a/src/components/dashboard/MetricsTab.tsx +++ b/src/components/dashboard/MetricsTab.tsx @@ -3,15 +3,13 @@ import { useState } from 'react'; import { useWeeklyMetrics, useVelocityTrends } from '@/hooks/use-metrics'; import { getToday } from '@/lib/date-utils'; -import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import { Card, CardContent } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; -import { DailyStatusChart } from './charts/DailyStatusChart'; -import { CompletionRateChart } from './charts/CompletionRateChart'; -import { StatusDistributionChart } from './charts/StatusDistributionChart'; -import { PriorityBreakdownChart } from './charts/PriorityBreakdownChart'; -import { VelocityTrendChart } from './charts/VelocityTrendChart'; -import { WeeklyActivityHeatmap } from './charts/WeeklyActivityHeatmap'; -import { ProductivityInsights } from './charts/ProductivityInsights'; +import { MetricsOverview } from './charts/MetricsOverview'; +import { MetricsMainCharts } from './charts/MetricsMainCharts'; +import { MetricsDistributionCharts } from './charts/MetricsDistributionCharts'; +import { MetricsVelocitySection } from './charts/MetricsVelocitySection'; +import { MetricsProductivitySection } from './charts/MetricsProductivitySection'; import { format } from 'date-fns'; import { fr } from 'date-fns/locale'; @@ -36,23 +34,6 @@ export function MetricsTab({ className }: MetricsTabProps) { return `Semaine du ${format(metrics.period.start, 'dd MMM', { locale: fr })} au ${format(metrics.period.end, 'dd MMM yyyy', { locale: fr })}`; }; - const getTrendIcon = (trend: string) => { - switch (trend) { - case 'improving': return '📈'; - case 'declining': return '📉'; - case 'stable': return '➡️'; - default: return '📊'; - } - }; - - const getPatternIcon = (pattern: string) => { - switch (pattern) { - case 'consistent': return '🎯'; - case 'variable': return '📊'; - case 'weekend-heavy': return '📅'; - default: return '📋'; - } - }; if (metricsError || trendsError) { return ( @@ -107,150 +88,24 @@ export function MetricsTab({ className }: MetricsTabProps) { ) : metrics ? (
{/* Vue d'ensemble rapide */} - - -

🎯 Vue d'ensemble

-
- -
-
-
- {metrics.summary.totalTasksCompleted} -
-
Terminées
-
- -
-
- {metrics.summary.totalTasksCreated} -
-
Créées
-
- -
-
- {metrics.summary.averageCompletionRate.toFixed(1)}% -
-
Taux moyen
-
- -
-
- {getTrendIcon(metrics.summary.trendsAnalysis.completionTrend)} -
-
- {metrics.summary.trendsAnalysis.completionTrend} -
-
- -
-
- {getPatternIcon(metrics.summary.trendsAnalysis.productivityPattern)} -
-
- {metrics.summary.trendsAnalysis.productivityPattern === 'consistent' ? 'Régulier' : - metrics.summary.trendsAnalysis.productivityPattern === 'variable' ? 'Variable' : 'Weekend+'} -
-
-
-
-
+ {/* Graphiques principaux */} -
- - -

📈 Évolution quotidienne des statuts

-
- - - -
- - - -

🎯 Taux de completion quotidien

-
- - - -
-
+ {/* Distribution et priorités */} -
- - -

🍰 Répartition des statuts

-
- - - -
- - - -

⚡ Performance par priorité

-
- - - -
- - - -

🔥 Heatmap d'activité

-
- - - -
-
+ {/* Tendances de vélocité */} - - -
-

🚀 Tendances de vélocité

- -
-
- - {trendsLoading ? ( -
-
-
-
-
-
- ) : trends.length > 0 ? ( - - ) : ( -
- Aucune donnée de vélocité disponible -
- )} -
-
+ {/* Analyses de productivité */} - - -

💡 Analyses de productivité

-
- - - -
+
) : null} diff --git a/src/components/dashboard/charts/MetricsDistributionCharts.tsx b/src/components/dashboard/charts/MetricsDistributionCharts.tsx new file mode 100644 index 0000000..fa52318 --- /dev/null +++ b/src/components/dashboard/charts/MetricsDistributionCharts.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import { StatusDistributionChart } from './StatusDistributionChart'; +import { PriorityBreakdownChart } from './PriorityBreakdownChart'; +import { WeeklyActivityHeatmap } from './WeeklyActivityHeatmap'; +import { WeeklyMetrics } from '@/hooks/use-metrics'; + +interface MetricsDistributionChartsProps { + metrics: WeeklyMetrics; +} + +export function MetricsDistributionCharts({ metrics }: MetricsDistributionChartsProps) { + return ( +
+ + +

🍰 Répartition des statuts

+
+ + + +
+ + + +

⚡ Performance par priorité

+
+ + + +
+ + + +

🔥 Heatmap d'activité

+
+ + + +
+
+ ); +} diff --git a/src/components/dashboard/charts/MetricsMainCharts.tsx b/src/components/dashboard/charts/MetricsMainCharts.tsx new file mode 100644 index 0000000..a0bd2d1 --- /dev/null +++ b/src/components/dashboard/charts/MetricsMainCharts.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import { DailyStatusChart } from './DailyStatusChart'; +import { CompletionRateChart } from './CompletionRateChart'; +import { WeeklyMetrics } from '@/hooks/use-metrics'; + +interface MetricsMainChartsProps { + metrics: WeeklyMetrics; +} + +export function MetricsMainCharts({ metrics }: MetricsMainChartsProps) { + return ( +
+ + +

📈 Évolution quotidienne des statuts

+
+ + + +
+ + + +

🎯 Taux de completion quotidien

+
+ + + +
+
+ ); +} diff --git a/src/components/dashboard/charts/MetricsOverview.tsx b/src/components/dashboard/charts/MetricsOverview.tsx new file mode 100644 index 0000000..c06358d --- /dev/null +++ b/src/components/dashboard/charts/MetricsOverview.tsx @@ -0,0 +1,79 @@ +'use client'; + +import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import { WeeklyMetrics } from '@/hooks/use-metrics'; + +interface MetricsOverviewProps { + metrics: WeeklyMetrics; +} + +export function MetricsOverview({ metrics }: MetricsOverviewProps) { + const getTrendIcon = (trend: string) => { + switch (trend) { + case 'improving': return '📈'; + case 'declining': return '📉'; + case 'stable': return '➡️'; + default: return '📊'; + } + }; + + const getPatternIcon = (pattern: string) => { + switch (pattern) { + case 'consistent': return '🎯'; + case 'variable': return '📊'; + case 'weekend-heavy': return '📅'; + default: return '📋'; + } + }; + + return ( + + +

🎯 Vue d'ensemble

+
+ +
+
+
+ {metrics.summary.totalTasksCompleted} +
+
Terminées
+
+ +
+
+ {metrics.summary.totalTasksCreated} +
+
Créées
+
+ +
+
+ {metrics.summary.averageCompletionRate.toFixed(1)}% +
+
Taux moyen
+
+ +
+
+ {getTrendIcon(metrics.summary.trendsAnalysis.completionTrend)} +
+
+ {metrics.summary.trendsAnalysis.completionTrend} +
+
+ +
+
+ {getPatternIcon(metrics.summary.trendsAnalysis.productivityPattern)} +
+
+ {metrics.summary.trendsAnalysis.productivityPattern === 'consistent' ? 'Régulier' : + metrics.summary.trendsAnalysis.productivityPattern === 'variable' ? 'Variable' : 'Weekend+'} +
+
+
+
+
+ ); +} diff --git a/src/components/dashboard/charts/MetricsProductivitySection.tsx b/src/components/dashboard/charts/MetricsProductivitySection.tsx new file mode 100644 index 0000000..c7bace4 --- /dev/null +++ b/src/components/dashboard/charts/MetricsProductivitySection.tsx @@ -0,0 +1,22 @@ +'use client'; + +import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import { ProductivityInsights } from './ProductivityInsights'; +import { WeeklyMetrics } from '@/hooks/use-metrics'; + +interface MetricsProductivitySectionProps { + metrics: WeeklyMetrics; +} + +export function MetricsProductivitySection({ metrics }: MetricsProductivitySectionProps) { + return ( + + +

💡 Analyses de productivité

+
+ + + +
+ ); +} diff --git a/src/components/dashboard/charts/MetricsVelocitySection.tsx b/src/components/dashboard/charts/MetricsVelocitySection.tsx new file mode 100644 index 0000000..c04d0db --- /dev/null +++ b/src/components/dashboard/charts/MetricsVelocitySection.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import { VelocityTrendChart } from './VelocityTrendChart'; +import { VelocityTrend } from '@/hooks/use-metrics'; + +interface MetricsVelocitySectionProps { + trends: VelocityTrend[]; + trendsLoading: boolean; + weeksBack: number; + onWeeksBackChange: (weeks: number) => void; +} + +export function MetricsVelocitySection({ + trends, + trendsLoading, + weeksBack, + onWeeksBackChange +}: MetricsVelocitySectionProps) { + return ( + + +
+

🚀 Tendances de vélocité

+ +
+
+ + {trendsLoading ? ( +
+
+
+
+
+
+ ) : trends.length > 0 ? ( + + ) : ( +
+ Aucune donnée de vélocité disponible +
+ )} +
+
+ ); +} diff --git a/src/components/forms/EditTaskForm.tsx b/src/components/forms/EditTaskForm.tsx index 840769f..434059e 100644 --- a/src/components/forms/EditTaskForm.tsx +++ b/src/components/forms/EditTaskForm.tsx @@ -3,15 +3,10 @@ import { useState, useEffect } from 'react'; import { Modal } from '@/components/ui/Modal'; import { Button } from '@/components/ui/Button'; -import { Input } from '@/components/ui/Input'; -import { TagInput } from '@/components/ui/TagInput'; -import { RelatedTodos } from '@/components/forms/RelatedTodos'; -import { Badge } from '@/components/ui/Badge'; import { Task, TaskPriority, TaskStatus } from '@/lib/types'; -import { useUserPreferences } from '@/contexts/UserPreferencesContext'; -// UpdateTaskData removed - using Server Actions directly -import { getAllStatuses, getAllPriorities } from '@/lib/status-config'; -import { formatDateForDateTimeInput, parseDateTimeInput } from '@/lib/date-utils'; +import { TaskBasicFields } from './task/TaskBasicFields'; +import { TaskJiraInfo } from './task/TaskJiraInfo'; +import { TaskTagsSection } from './task/TaskTagsSection'; interface EditTaskFormProps { isOpen: boolean; @@ -22,7 +17,6 @@ interface EditTaskFormProps { } export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false }: EditTaskFormProps) { - const { preferences } = useUserPreferences(); const [formData, setFormData] = useState<{ title: string; description: string; @@ -41,13 +35,6 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false const [errors, setErrors] = useState>({}); - // Helper pour construire l'URL Jira - const getJiraTicketUrl = (jiraKey: string): string => { - const baseUrl = preferences.jiraConfig.baseUrl; - if (!baseUrl || !jiraKey) return ''; - return `${baseUrl}/browse/${jiraKey}`; - }; - // Pré-remplir le formulaire quand la tâche change useEffect(() => { if (task) { @@ -108,149 +95,29 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false return (
- {/* Titre */} - setFormData(prev => ({ ...prev, title: e.target.value }))} - placeholder="Titre de la tâche..." - error={errors.title} - disabled={loading} + setFormData(prev => ({ ...prev, title }))} + onDescriptionChange={(description) => setFormData(prev => ({ ...prev, description }))} + onPriorityChange={(priority) => setFormData(prev => ({ ...prev, priority }))} + onStatusChange={(status) => setFormData(prev => ({ ...prev, status }))} + onDueDateChange={(dueDate) => setFormData(prev => ({ ...prev, dueDate }))} + errors={errors} + loading={loading} /> - {/* Description */} -
- -