'use client'; import { useState, useEffect } from 'react'; import { detectJiraAnomalies, updateAnomalyDetectionConfig, getAnomalyDetectionConfig } from '@/actions/jira-anomalies'; import { JiraAnomaly, AnomalyDetectionConfig } from '@/services/jira-anomaly-detection'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { Modal } from '@/components/ui/Modal'; import { Card, CardHeader, CardContent } from '@/components/ui/Card'; interface AnomalyDetectionPanelProps { className?: string; } export default function AnomalyDetectionPanel({ className = '' }: AnomalyDetectionPanelProps) { const [anomalies, setAnomalies] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showConfig, setShowConfig] = useState(false); const [config, setConfig] = useState(null); const [lastUpdate, setLastUpdate] = useState(null); const [isExpanded, setIsExpanded] = useState(false); // Charger la config au montage, les anomalies seulement si expanded useEffect(() => { loadConfig(); }, []); // Charger les anomalies quand on ouvre le panneau useEffect(() => { if (isExpanded && anomalies.length === 0) { loadAnomalies(); } }, [isExpanded, anomalies.length]); const loadAnomalies = async (forceRefresh = false) => { setLoading(true); setError(null); try { const result = await detectJiraAnomalies(forceRefresh); if (result.success && result.data) { setAnomalies(result.data); setLastUpdate(new Date().toLocaleString('fr-FR')); } else { setError(result.error || 'Erreur lors de la détection'); } } catch { setError('Erreur de connexion'); } finally { setLoading(false); } }; const loadConfig = async () => { try { const result = await getAnomalyDetectionConfig(); if (result.success && result.data) { setConfig(result.data); } } catch (err) { console.error('Erreur lors du chargement de la config:', err); } }; const handleConfigUpdate = async (newConfig: AnomalyDetectionConfig) => { try { const result = await updateAnomalyDetectionConfig(newConfig); if (result.success && result.data) { setConfig(result.data); setShowConfig(false); // Recharger les anomalies avec la nouvelle config loadAnomalies(true); } } catch (err) { console.error('Erreur lors de la mise à jour de la config:', err); } }; const getSeverityColor = (severity: string): string => { switch (severity) { case 'critical': return 'bg-red-100 text-red-800 border-red-200'; case 'high': return 'bg-orange-100 text-orange-800 border-orange-200'; case 'medium': return 'bg-yellow-100 text-yellow-800 border-yellow-200'; case 'low': return 'bg-blue-100 text-blue-800 border-blue-200'; default: return 'bg-gray-100 text-gray-800 border-gray-200'; } }; const getSeverityIcon = (severity: string): string => { switch (severity) { case 'critical': return '🚨'; case 'high': return '⚠️'; case 'medium': return '⚡'; case 'low': return 'ℹ️'; default: return '📊'; } }; const criticalCount = anomalies.filter(a => a.severity === 'critical').length; const highCount = anomalies.filter(a => a.severity === 'high').length; const totalCount = anomalies.length; return ( setIsExpanded(!isExpanded)} >

🔍 Détection d'anomalies

{totalCount > 0 && (
{criticalCount > 0 && ( {criticalCount} critique{criticalCount > 1 ? 's' : ''} )} {highCount > 0 && ( {highCount} élevée{highCount > 1 ? 's' : ''} )}
)}
{isExpanded && (
e.stopPropagation()}>
)}
{isExpanded && lastUpdate && (

Dernière analyse: {lastUpdate}

)}
{isExpanded && ( {error && (

❌ {error}

)} {loading && (

Analyse en cours...

)} {!loading && !error && anomalies.length === 0 && (

Aucune anomalie détectée

Toutes les métriques sont dans les seuils normaux

)} {!loading && anomalies.length > 0 && (
{anomalies.map((anomaly) => (
{getSeverityIcon(anomaly.severity)}

{anomaly.title}

{anomaly.severity}

{anomaly.description}

Valeur: {anomaly.value.toFixed(1)} {anomaly.threshold > 0 && ( (seuil: {anomaly.threshold.toFixed(1)}) )}
{anomaly.affectedItems.length > 0 && (
{anomaly.affectedItems.slice(0, 2).map((item, index) => ( {item} ))} {anomaly.affectedItems.length > 2 && ( +{anomaly.affectedItems.length - 2} )}
)}
))}
)}
)} {/* Modal de configuration */} {showConfig && config && ( setShowConfig(false)} title="Configuration de la détection d'anomalies" >
setConfig({...config, velocityVarianceThreshold: Number(e.target.value)})} className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm" min="0" max="100" />

Pourcentage de variance acceptable dans la vélocité

setConfig({...config, cycleTimeThreshold: Number(e.target.value)})} className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm" min="1" max="5" />

Multiplicateur au-delà duquel le cycle time est considéré anormal

setConfig({...config, workloadImbalanceThreshold: Number(e.target.value)})} className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm" min="1" max="10" />

Ratio maximum acceptable entre les charges de travail

setConfig({...config, completionRateThreshold: Number(e.target.value)})} className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm" min="0" max="100" />

Pourcentage minimum de completion des sprints

)}
); }