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:
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { JiraConfig } from '@/lib/types';
|
import { JiraConfig, JiraAnalytics } from '@/lib/types';
|
||||||
import { useJiraAnalytics } from '@/hooks/useJiraAnalytics';
|
import { useJiraAnalytics } from '@/hooks/useJiraAnalytics';
|
||||||
import { useJiraExport } from '@/hooks/useJiraExport';
|
import { useJiraExport } from '@/hooks/useJiraExport';
|
||||||
import { filterAnalyticsByPeriod, getPeriodInfo, type PeriodFilter } from '@/lib/jira-period-filter';
|
import { filterAnalyticsByPeriod, getPeriodInfo, type PeriodFilter } from '@/lib/jira-period-filter';
|
||||||
@@ -28,10 +28,11 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
interface JiraDashboardPageClientProps {
|
interface JiraDashboardPageClientProps {
|
||||||
initialJiraConfig: JiraConfig;
|
initialJiraConfig: JiraConfig;
|
||||||
|
initialAnalytics?: JiraAnalytics | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function JiraDashboardPageClient({ initialJiraConfig }: JiraDashboardPageClientProps) {
|
export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }: JiraDashboardPageClientProps) {
|
||||||
const { analytics: rawAnalytics, isLoading, error, loadAnalytics, refreshAnalytics } = useJiraAnalytics();
|
const { analytics: rawAnalytics, isLoading, error, loadAnalytics, refreshAnalytics } = useJiraAnalytics(initialAnalytics);
|
||||||
const { isExporting, error: exportError, exportCSV, exportJSON } = useJiraExport();
|
const { isExporting, error: exportError, exportCSV, exportJSON } = useJiraExport();
|
||||||
const {
|
const {
|
||||||
availableFilters,
|
availableFilters,
|
||||||
@@ -56,11 +57,11 @@ export function JiraDashboardPageClient({ initialJiraConfig }: JiraDashboardPage
|
|||||||
const periodInfo = getPeriodInfo(selectedPeriod);
|
const periodInfo = getPeriodInfo(selectedPeriod);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Charger les analytics au montage si Jira est configuré avec un projet
|
// 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) {
|
if (initialJiraConfig.enabled && initialJiraConfig.projectKey && !initialAnalytics) {
|
||||||
loadAnalytics();
|
loadAnalytics();
|
||||||
}
|
}
|
||||||
}, [initialJiraConfig.enabled, initialJiraConfig.projectKey, loadAnalytics]);
|
}, [initialJiraConfig.enabled, initialJiraConfig.projectKey, loadAnalytics, initialAnalytics]);
|
||||||
|
|
||||||
// Gestion du clic sur un sprint
|
// Gestion du clic sur un sprint
|
||||||
const handleSprintClick = (sprint: SprintVelocity) => {
|
const handleSprintClick = (sprint: SprintVelocity) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { userPreferencesService } from '@/services/core/user-preferences';
|
import { userPreferencesService } from '@/services/core/user-preferences';
|
||||||
|
import { getJiraAnalytics } from '@/actions/jira-analytics';
|
||||||
import { JiraDashboardPageClient } from './JiraDashboardPageClient';
|
import { JiraDashboardPageClient } from './JiraDashboardPageClient';
|
||||||
|
|
||||||
// Force dynamic rendering
|
// Force dynamic rendering
|
||||||
@@ -8,7 +9,19 @@ export default async function JiraDashboardPage() {
|
|||||||
// Récupérer la config Jira côté serveur
|
// Récupérer la config Jira côté serveur
|
||||||
const jiraConfig = await userPreferencesService.getJiraConfig();
|
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 (
|
return (
|
||||||
<JiraDashboardPageClient initialJiraConfig={jiraConfig} />
|
<JiraDashboardPageClient
|
||||||
|
initialJiraConfig={jiraConfig}
|
||||||
|
initialAnalytics={initialAnalytics}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { JiraAnalytics } from '@/lib/types';
|
import { JiraAnalytics } from '@/lib/types';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
|
|
||||||
@@ -23,10 +23,16 @@ interface CollaborationData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CollaborationMatrix({ analytics, className }: CollaborationMatrixProps) {
|
export function CollaborationMatrix({ analytics, className }: CollaborationMatrixProps) {
|
||||||
// Analyser les patterns de collaboration basés sur les données existantes
|
const [collaborationData, setCollaborationData] = useState<CollaborationData[]>([]);
|
||||||
const collaborationData: CollaborationData[] = analytics.teamMetrics.issuesDistribution.map(assignee => {
|
const [isClient, setIsClient] = useState(false);
|
||||||
// Simuler des collaborations basées sur les données réelles
|
|
||||||
const totalTickets = assignee.totalIssues;
|
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
|
// Générer des partenaires de collaboration réalistes
|
||||||
const otherAssignees = analytics.teamMetrics.issuesDistribution.filter(a => a.assignee !== assignee.assignee);
|
const otherAssignees = analytics.teamMetrics.issuesDistribution.filter(a => a.assignee !== assignee.assignee);
|
||||||
@@ -67,13 +73,25 @@ export function CollaborationMatrix({ analytics, className }: CollaborationMatri
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Statistiques globales
|
setCollaborationData(data);
|
||||||
const avgCollaboration = collaborationData.reduce((sum, d) => sum + d.collaborationScore, 0) / collaborationData.length;
|
}, [analytics]);
|
||||||
const avgIsolation = collaborationData.reduce((sum, d) => sum + d.isolation, 0) / collaborationData.length;
|
|
||||||
const mostCollaborative = collaborationData.reduce((max, current) =>
|
// Ne pas rendre côté serveur pour éviter l'erreur d'hydratation
|
||||||
current.collaborationScore > max.collaborationScore ? current : max, collaborationData[0]);
|
if (!isClient) {
|
||||||
const mostIsolated = collaborationData.reduce((max, current) =>
|
return (
|
||||||
current.isolation > max.isolation ? current : max, collaborationData[0]);
|
<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é
|
// Couleur d'intensité
|
||||||
const getIntensityColor = (intensity: 'low' | 'medium' | 'high') => {
|
const getIntensityColor = (intensity: 'low' | 'medium' | 'high') => {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useState, useTransition, useCallback } from 'react';
|
|||||||
import { getJiraAnalytics } from '@/actions/jira-analytics';
|
import { getJiraAnalytics } from '@/actions/jira-analytics';
|
||||||
import { JiraAnalytics } from '@/lib/types';
|
import { JiraAnalytics } from '@/lib/types';
|
||||||
|
|
||||||
export function useJiraAnalytics() {
|
export function useJiraAnalytics(initialAnalytics?: JiraAnalytics | null) {
|
||||||
const [analytics, setAnalytics] = useState<JiraAnalytics | null>(null);
|
const [analytics, setAnalytics] = useState<JiraAnalytics | null>(initialAnalytics || null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user