feat: enhance JiraDashboard with initial analytics support

- Updated `JiraDashboardPageClient` to accept `initialAnalytics`, allowing for server-side analytics retrieval.
- Modified `useJiraAnalytics` to initialize state with `initialAnalytics`, improving data handling.
- Adjusted `CollaborationMatrix` to manage client-side rendering and analytics data processing, preventing hydration errors.
- Enhanced `page.tsx` to fetch analytics based on Jira configuration, ensuring data is available for the dashboard.
This commit is contained in:
Julien Froidefond
2025-09-26 11:42:08 +02:00
parent b87fa64d4d
commit 350dbe6479
4 changed files with 53 additions and 21 deletions

View File

@@ -1,7 +1,7 @@
'use client';
import { useState, useEffect, useMemo } from 'react';
import { JiraConfig } from '@/lib/types';
import { JiraConfig, JiraAnalytics } from '@/lib/types';
import { useJiraAnalytics } from '@/hooks/useJiraAnalytics';
import { useJiraExport } from '@/hooks/useJiraExport';
import { filterAnalyticsByPeriod, getPeriodInfo, type PeriodFilter } from '@/lib/jira-period-filter';
@@ -28,10 +28,11 @@ import Link from 'next/link';
interface JiraDashboardPageClientProps {
initialJiraConfig: JiraConfig;
initialAnalytics?: JiraAnalytics | null;
}
export function JiraDashboardPageClient({ initialJiraConfig }: JiraDashboardPageClientProps) {
const { analytics: rawAnalytics, isLoading, error, loadAnalytics, refreshAnalytics } = useJiraAnalytics();
export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }: JiraDashboardPageClientProps) {
const { analytics: rawAnalytics, isLoading, error, loadAnalytics, refreshAnalytics } = useJiraAnalytics(initialAnalytics);
const { isExporting, error: exportError, exportCSV, exportJSON } = useJiraExport();
const {
availableFilters,
@@ -56,11 +57,11 @@ export function JiraDashboardPageClient({ initialJiraConfig }: JiraDashboardPage
const periodInfo = getPeriodInfo(selectedPeriod);
useEffect(() => {
// Charger les analytics au montage si Jira est configuré avec un projet
if (initialJiraConfig.enabled && initialJiraConfig.projectKey) {
// Charger les analytics au montage seulement si Jira est configuré ET qu'on n'a pas déjà des données
if (initialJiraConfig.enabled && initialJiraConfig.projectKey && !initialAnalytics) {
loadAnalytics();
}
}, [initialJiraConfig.enabled, initialJiraConfig.projectKey, loadAnalytics]);
}, [initialJiraConfig.enabled, initialJiraConfig.projectKey, loadAnalytics, initialAnalytics]);
// Gestion du clic sur un sprint
const handleSprintClick = (sprint: SprintVelocity) => {

View File

@@ -1,4 +1,5 @@
import { userPreferencesService } from '@/services/core/user-preferences';
import { getJiraAnalytics } from '@/actions/jira-analytics';
import { JiraDashboardPageClient } from './JiraDashboardPageClient';
// Force dynamic rendering
@@ -7,8 +8,20 @@ export const dynamic = 'force-dynamic';
export default async function JiraDashboardPage() {
// Récupérer la config Jira côté serveur
const jiraConfig = await userPreferencesService.getJiraConfig();
// Récupérer les analytics côté serveur (utilise le cache du service)
let initialAnalytics = null;
if (jiraConfig.enabled && jiraConfig.projectKey) {
const analyticsResult = await getJiraAnalytics(false); // Utilise le cache
if (analyticsResult.success) {
initialAnalytics = analyticsResult.data;
}
}
return (
<JiraDashboardPageClient initialJiraConfig={jiraConfig} />
<JiraDashboardPageClient
initialJiraConfig={jiraConfig}
initialAnalytics={initialAnalytics}
/>
);
}

View File

@@ -1,6 +1,6 @@
'use client';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { JiraAnalytics } from '@/lib/types';
import { Card } from '@/components/ui/Card';
@@ -23,10 +23,16 @@ interface CollaborationData {
}
export function CollaborationMatrix({ analytics, className }: CollaborationMatrixProps) {
// Analyser les patterns de collaboration basés sur les données existantes
const collaborationData: CollaborationData[] = analytics.teamMetrics.issuesDistribution.map(assignee => {
// Simuler des collaborations basées sur les données réelles
const totalTickets = assignee.totalIssues;
const [collaborationData, setCollaborationData] = useState<CollaborationData[]>([]);
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
// Analyser les patterns de collaboration basés sur les données existantes
const data: CollaborationData[] = analytics.teamMetrics.issuesDistribution.map(assignee => {
// Simuler des collaborations basées sur les données réelles
const totalTickets = assignee.totalIssues;
// Générer des partenaires de collaboration réalistes
const otherAssignees = analytics.teamMetrics.issuesDistribution.filter(a => a.assignee !== assignee.assignee);
@@ -67,13 +73,25 @@ export function CollaborationMatrix({ analytics, className }: CollaborationMatri
};
});
// Statistiques globales
const avgCollaboration = collaborationData.reduce((sum, d) => sum + d.collaborationScore, 0) / collaborationData.length;
const avgIsolation = collaborationData.reduce((sum, d) => sum + d.isolation, 0) / collaborationData.length;
const mostCollaborative = collaborationData.reduce((max, current) =>
current.collaborationScore > max.collaborationScore ? current : max, collaborationData[0]);
const mostIsolated = collaborationData.reduce((max, current) =>
current.isolation > max.isolation ? current : max, collaborationData[0]);
setCollaborationData(data);
}, [analytics]);
// Ne pas rendre côté serveur pour éviter l'erreur d'hydratation
if (!isClient) {
return (
<div className={className}>
<div className="animate-pulse bg-gray-200 dark:bg-gray-700 rounded-lg h-96" />
</div>
);
}
// Statistiques globales
const avgCollaboration = collaborationData.reduce((sum, d) => sum + d.collaborationScore, 0) / collaborationData.length;
const avgIsolation = collaborationData.reduce((sum, d) => sum + d.isolation, 0) / collaborationData.length;
const mostCollaborative = collaborationData.reduce((max, current) =>
current.collaborationScore > max.collaborationScore ? current : max, collaborationData[0]);
const mostIsolated = collaborationData.reduce((max, current) =>
current.isolation > max.isolation ? current : max, collaborationData[0]);
// Couleur d'intensité
const getIntensityColor = (intensity: 'low' | 'medium' | 'high') => {

View File

@@ -4,8 +4,8 @@ import { useState, useTransition, useCallback } from 'react';
import { getJiraAnalytics } from '@/actions/jira-analytics';
import { JiraAnalytics } from '@/lib/types';
export function useJiraAnalytics() {
const [analytics, setAnalytics] = useState<JiraAnalytics | null>(null);
export function useJiraAnalytics(initialAnalytics?: JiraAnalytics | null) {
const [analytics, setAnalytics] = useState<JiraAnalytics | null>(initialAnalytics || null);
const [error, setError] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();