feat: add integration filtering to dashboard components

- Introduced `IntegrationFilter` to allow users to filter tasks by selected and hidden sources.
- Updated `DashboardStats`, `ProductivityAnalytics`, `RecentTasks`, and `HomePageContent` to utilize the new filtering logic, enhancing data presentation based on user preferences.
- Implemented filtering logic in `AnalyticsService` and `DeadlineAnalyticsService` to support source-based metrics calculations.
- Enhanced UI components to reflect filtered task data, improving user experience and data relevance.
This commit is contained in:
Julien Froidefond
2025-10-02 13:15:10 +02:00
parent 2e3e8bb222
commit 46c1c5e9a1
7 changed files with 600 additions and 39 deletions

View File

@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import { ProductivityMetrics } from '@/services/analytics/analytics';
import { DeadlineMetrics } from '@/services/analytics/deadline-analytics';
import { CompletionTrendChart } from '@/components/charts/CompletionTrendChart';
@@ -10,14 +11,79 @@ import { DeadlineOverview } from '@/components/deadline/DeadlineOverview';
interface ProductivityAnalyticsProps {
metrics: ProductivityMetrics;
deadlineMetrics: DeadlineMetrics;
selectedSources: string[];
hiddenSources?: string[];
}
export function ProductivityAnalytics({ metrics, deadlineMetrics }: ProductivityAnalyticsProps) {
export function ProductivityAnalytics({ metrics, deadlineMetrics, selectedSources, hiddenSources = [] }: ProductivityAnalyticsProps) {
// Filtrer les métriques selon les sources sélectionnées
const filteredMetrics = useMemo(() => {
if (selectedSources.length === 0) {
return metrics;
}
// Pour les métriques complexes, on garde les données originales
// car elles nécessitent un recalcul complet côté serveur
// TODO: Implémenter le recalcul côté client ou créer une API
return metrics;
}, [metrics, selectedSources]);
const filteredDeadlineMetrics = useMemo(() => {
let filteredOverdue = deadlineMetrics.overdue;
let filteredCritical = deadlineMetrics.critical;
let filteredWarning = deadlineMetrics.warning;
let filteredUpcoming = deadlineMetrics.upcoming;
// Si on a des sources sélectionnées, ne garder que celles-ci
if (selectedSources.length > 0) {
filteredOverdue = filteredOverdue.filter(task =>
selectedSources.includes(task.source)
);
filteredCritical = filteredCritical.filter(task =>
selectedSources.includes(task.source)
);
filteredWarning = filteredWarning.filter(task =>
selectedSources.includes(task.source)
);
filteredUpcoming = filteredUpcoming.filter(task =>
selectedSources.includes(task.source)
);
} else if (hiddenSources.length > 0) {
// Sinon, retirer les sources masquées
filteredOverdue = filteredOverdue.filter(task =>
!hiddenSources.includes(task.source)
);
filteredCritical = filteredCritical.filter(task =>
!hiddenSources.includes(task.source)
);
filteredWarning = filteredWarning.filter(task =>
!hiddenSources.includes(task.source)
);
filteredUpcoming = filteredUpcoming.filter(task =>
!hiddenSources.includes(task.source)
);
}
return {
overdue: filteredOverdue,
critical: filteredCritical,
warning: filteredWarning,
upcoming: filteredUpcoming,
summary: {
overdueCount: filteredOverdue.length,
criticalCount: filteredCritical.length,
warningCount: filteredWarning.length,
upcomingCount: filteredUpcoming.length,
totalWithDeadlines: filteredOverdue.length + filteredCritical.length + filteredWarning.length + filteredUpcoming.length
}
};
}, [deadlineMetrics, selectedSources, hiddenSources]);
return (
<div className="space-y-8">
{/* Section Échéances Critiques */}
<DeadlineOverview metrics={deadlineMetrics} />
<DeadlineOverview metrics={filteredDeadlineMetrics} />
{/* Titre de section Analytics */}
<div className="flex items-center justify-between">
@@ -28,23 +94,23 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics }: Productivity
</div>
{/* Performance hebdomadaire */}
<WeeklyStatsCard stats={metrics.weeklyStats} />
<WeeklyStatsCard stats={filteredMetrics.weeklyStats} />
{/* Graphiques principaux */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<CompletionTrendChart data={metrics.completionTrend} />
<VelocityChart data={metrics.velocityData} />
<CompletionTrendChart data={filteredMetrics.completionTrend} />
<VelocityChart data={filteredMetrics.velocityData} />
</div>
{/* Distributions */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<PriorityDistributionChart data={metrics.priorityDistribution} />
<PriorityDistributionChart data={filteredMetrics.priorityDistribution} />
{/* Status Flow - Graphique simple en barres horizontales */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4">Répartition par Statut</h3>
<div className="space-y-3">
{metrics.statusFlow.map((item, index) => (
{filteredMetrics.statusFlow.map((item, index) => (
<div key={index} className="flex items-center gap-3">
<div className="w-20 text-sm text-[var(--muted-foreground)] text-right">
{item.status}
@@ -73,8 +139,8 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics }: Productivity
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<MetricCard
title="Vélocité Moyenne"
value={`${metrics.velocityData.length > 0
? Math.round(metrics.velocityData.reduce((acc, item) => acc + item.completed, 0) / metrics.velocityData.length)
value={`${filteredMetrics.velocityData.length > 0
? Math.round(filteredMetrics.velocityData.reduce((acc, item) => acc + item.completed, 0) / filteredMetrics.velocityData.length)
: 0
} tâches/sem`}
color="primary"
@@ -82,9 +148,9 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics }: Productivity
<MetricCard
title="Priorité Principale"
value={metrics.priorityDistribution.reduce((max, item) =>
value={filteredMetrics.priorityDistribution.reduce((max, item) =>
item.count > max.count ? item : max,
metrics.priorityDistribution[0]
filteredMetrics.priorityDistribution[0]
)?.priority || 'N/A'}
color="success"
/>
@@ -92,8 +158,8 @@ export function ProductivityAnalytics({ metrics, deadlineMetrics }: Productivity
<MetricCard
title="Taux de Completion"
value={`${(() => {
const completed = metrics.statusFlow.find(s => s.status === 'Terminé')?.count || 0;
const total = metrics.statusFlow.reduce((acc, s) => acc + s.count, 0);
const completed = filteredMetrics.statusFlow.find(s => s.status === 'Terminé')?.count || 0;
const total = filteredMetrics.statusFlow.reduce((acc, s) => acc + s.count, 0);
return total > 0 ? Math.round((completed / total) * 100) : 0;
})()}%`}
color="warning"