From 6ecf94dc9f6f9d9c0882a2171c96b6710146ed4c Mon Sep 17 00:00:00 2001 From: syoul Date: Tue, 9 Dec 2025 14:18:08 +0100 Subject: [PATCH] fix: vraie page React pour /team avec import dynamique MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remplace HTML intégré par vraie page React - Import dynamique de Cytoscape et ECharts (évite erreurs SSR) - États React pour gestion des onglets et données - Refs pour containers des graphiques - Installation dépendances cytoscape et echarts-for-react - Gestion erreurs et états de chargement Résout problème scripts non exécutés dans contexte React --- Dockerfile.business | 478 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 469 insertions(+), 9 deletions(-) diff --git a/Dockerfile.business b/Dockerfile.business index 0ffe434..72b5fdc 100644 --- a/Dockerfile.business +++ b/Dockerfile.business @@ -32,7 +32,7 @@ RUN apk add --no-cache git python3 COPY package.json package-lock.json* ./ # Installation des dépendances Node -RUN npm install --legacy-peer-deps --ignore-scripts +RUN npm install --legacy-peer-deps --ignore-scripts cytoscape cytoscape-cose-bilkent echarts-for-react # Patch du package aoe_technology_radar pour inclure gray-matter dans les dépendances runtime RUN node -e "const fs=require('fs');const pkgPath='./node_modules/aoe_technology_radar/package.json';const pkg=JSON.parse(fs.readFileSync(pkgPath,'utf8'));pkg.dependencies=pkg.dependencies||{};pkg.dependencies['gray-matter']='^4.0.3';pkg.dependencies['postcss']='^8.4.47';pkg.scripts=pkg.scripts||{};pkg.scripts.prepare='';fs.writeFileSync(pkgPath,JSON.stringify(pkg,null,2));" @@ -50,7 +50,7 @@ RUN mkdir -p .techradar && \ RUN node -e "const crypto=require('crypto');const fs=require('fs');const hash=crypto.createHash('sha256').update(fs.readFileSync('package.json')).digest('hex');fs.writeFileSync('.techradar/hash',hash);" RUN node -e "const fs=require('fs');const p='.techradar/package.json';if(!fs.existsSync(p)){console.error('.techradar/package.json not found');process.exit(1);}const pkg=JSON.parse(fs.readFileSync(p,'utf8'));pkg.scripts=pkg.scripts||{};pkg.scripts.prepare='';fs.writeFileSync(p,JSON.stringify(pkg,null,2));" # Installer les dépendances dans .techradar (y compris devDependencies pour tsx nécessaire à build:data) -RUN cd .techradar && npm install --legacy-peer-deps --include=dev +RUN cd .techradar && npm install --legacy-peer-deps --include=dev cytoscape cytoscape-cose-bilkent echarts-for-react RUN cd .techradar && npm run build:icons # --- CONFIGURATION BUSINESS --- @@ -101,18 +101,478 @@ RUN echo "📊 Comptage des fichiers .md dans .techradar/data/radar" && \ # La page Next.js pour le routing, le HTML statique pour garantir l'affichage RUN mkdir -p .techradar/src/pages && \ cat > .techradar/src/pages/team.tsx << 'EOF' -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; +import dynamic from 'next/dynamic'; + +// Chargement dynamique des bibliothèques pour éviter les erreurs SSR +const CytoscapeComponent = dynamic(() => import('cytoscape'), { ssr: false }); +const EChartsComponent = dynamic(() => import('echarts-for-react'), { ssr: false }); export default function TeamPage() { - const [htmlContent, setHtmlContent] = useState(''); + const [activeTab, setActiveTab] = useState('network'); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const networkRef = useRef(null); + const matrixRef = useRef(null); useEffect(() => { - // Intégrer directement le HTML statique dans la page React - // Cela évite les problèmes de routing Next.js - console.log('🔄 TEAM PAGE: Chargement direct du contenu HTML intégré'); + // Charger les données équipe + loadTeamData(); + }, []); - // Le contenu HTML est intégré directement dans le composant - const staticHtml = ` + useEffect(() => { + // Initialiser les graphiques quand les données sont chargées et l'onglet actif change + if (data && !loading) { + if (activeTab === 'network') { + initNetworkGraph(); + } else if (activeTab === 'congestion') { + initCongestionMatrix(); + } + } + }, [data, loading, activeTab]); + + const loadTeamData = async () => { + try { + console.log('🔄 Chargement des données équipe...'); + const response = await fetch('/team-visualization-data.json'); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + const jsonData = await response.json(); + console.log('✅ Données chargées:', Object.keys(jsonData)); + setData(jsonData); + } catch (err) { + console.error('❌ Erreur chargement données:', err); + setError(err.message); + } finally { + setLoading(false); + } + }; + + const initNetworkGraph = () => { + if (!data?.network || !networkRef.current) return; + + console.log('📊 Initialisation graphe réseau...'); + + // Import dynamique pour éviter les erreurs SSR + import('cytoscape').then((cytoscape) => { + import('cytoscape-cose-bilkent').then(() => { + const cy = cytoscape.default({ + container: networkRef.current, + elements: data.network, + style: [ + { + selector: 'node[type="technology"]', + style: { + 'background-color': 'data(color)', + 'label': 'data(label)', + 'width': function(ele) { + const coverage = ele.data('coverage') || 0; + return Math.max(30, 30 + (coverage * 8)); + }, + 'height': function(ele) { + const coverage = ele.data('coverage') || 0; + return Math.max(30, 30 + (coverage * 8)); + }, + 'font-size': '12px', + 'text-valign': 'center', + 'text-halign': 'center', + 'color': '#ffffff', + 'text-outline-color': '#1a4d3a', + 'text-outline-width': '2px' + } + }, + { + selector: 'node[type="member"]', + style: { + 'background-color': '#88ff88', + 'label': 'data(label)', + 'width': function(ele) { + const availability = ele.data('availability') || 0; + return Math.max(25, 25 + (availability / 3)); + }, + 'height': function(ele) { + const availability = ele.data('availability') || 0; + return Math.max(25, 25 + (availability / 3)); + }, + 'font-size': '10px', + 'text-valign': 'center', + 'text-halign': 'center', + 'color': '#1a4d3a', + 'text-outline-color': '#ffffff', + 'text-outline-width': '1px' + } + }, + { + selector: 'edge', + style: { + 'width': function(ele) { + return 1 + (ele.data('weight') || 0.5); + }, + 'line-color': '#4ade80', + 'target-arrow-color': '#4ade80', + 'target-arrow-shape': 'triangle', + 'curve-style': 'bezier' + } + } + ], + layout: { + name: 'cose-bilkent', + animate: true, + animationDuration: 1000, + nodeRepulsion: 4500, + idealEdgeLength: 100, + edgeElasticity: 0.45 + } + }); + + // Gestionnaire de clics + cy.on('tap', 'node', function(evt) { + const node = evt.target; + const nodeData = node.data(); + let tooltip = ''; + + if (nodeData.type === 'technology') { + tooltip = \`\${nodeData.label}\\n\` + + \`Ring: \${nodeData.ring}\\n\` + + \`Couverture: \${nodeData.coverage} personne(s)\\n\` + + \`Impact: \${nodeData.businessImpact}\\n\` + + \`Gap: \${nodeData.skillGap}\`; + } else { + tooltip = \`\${nodeData.label}\\n\` + + \`Disponibilité: \${nodeData.availability}%\` + + (nodeData.role ? \`\\nRôle: \${nodeData.role}\` : ''); + } + + alert(tooltip); + }); + + console.log('✅ Graphe réseau initialisé'); + }); + }); + }; + + const initCongestionMatrix = () => { + if (!data?.congestionMatrix || !matrixRef.current) return; + + console.log('📈 Initialisation matrice congestion...'); + + // Import dynamique pour éviter les erreurs SSR + import('echarts-for-react').then(() => { + import('echarts').then((echarts) => { + const techs = data.congestionMatrix.map(r => r.technology); + const members = data.congestionMatrix[0]?.members.map(m => m.fullName || m.member) || []; + + const scatterData = []; + data.congestionMatrix.forEach((row, i) => { + row.members.forEach((member, j) => { + scatterData.push([j, i, member.availability, member]); + }); + }); + + const chart = echarts.default.init(matrixRef.current); + const option = { + title: { + text: 'Disponibilité des Technologies Core', + left: 'center', + textStyle: { color: '#e0e0e0' } + }, + tooltip: { + formatter: function(params) { + if (params.data && params.data[3]) { + const member = params.data[3]; + return \`\${member.fullName || member.member}
\` + + \`Technologie: \${techs[params.data[1]]}
\` + + \`Disponibilité: \${params.data[2]}%\`; + } + } + }, + grid: { + left: '10%', + right: '10%', + top: '15%', + bottom: '15%', + containLabel: true + }, + xAxis: { + type: 'category', + data: members, + axisLabel: { + color: '#e0e0e0', + rotate: 45 + } + }, + yAxis: { + type: 'category', + data: techs, + axisLabel: { color: '#e0e0e0' } + }, + visualMap: { + min: 0, + max: 100, + dimension: 2, + orient: 'horizontal', + left: 'center', + top: 'top', + text: ['Élevé', 'Faible'], + textStyle: { color: '#e0e0e0' }, + inRange: { + color: ['#ff4444', '#ff8800', '#4488ff', '#88ff88'] + }, + outOfRange: { + color: '#444444' + } + }, + series: [{ + name: 'Disponibilité', + type: 'scatter', + data: scatterData, + symbolSize: function(data) { + return 15 + (data[2] || 0) / 2; + } + }] + }; + + chart.setOption(option); + console.log('✅ Matrice congestion initialisée'); + }); + }); + }; + + const renderGenesisTeam = () => { + if (!data?.genesisTeam) return
Données équipe non disponibles
; + + const genesis = data.genesisTeam; + return ( +
+

Équipe de Genèse MVP

+
+

Critères de Sélection

+
    +
  • Disponibilité ≥ {genesis.minAvailability}%
  • +
  • Couverture technologique ≥ {genesis.minCoverage} technologies
  • +
  • Taille équipe optimale: {genesis.targetTeamSize} membres
  • +
+
+ +
+ {genesis.selectedMembers.map(member => ( +
+
+
+
{member.fullName || member.member}
+
+ {member.role || ''} • {member.seniority} • {member.coverage} technologie(s) +
+
+
+
{member.availability}%
+
Disponibilité
+
+
+
+ {member.skills.slice(0, 8).map(skill => ( + + {skill} + + ))} + {member.skills.length > 8 && ( + + +{member.skills.length - 8} + + )} +
+
+ ))} +
+
+ ); + }; + + if (loading) { + return ( +
+
+ Chargement des visualisations équipe... +
+
+ ); + } + + if (error) { + return ( +
+
+ Erreur: {error} +
+
+ ); + } + + return ( +
+
+
+
+ ← Retour au Radar +
+
+

👥 Équipe & Technologies

+

Visualisation des compétences et identification de l'équipe de genèse MVP

+
+ +
+ + + +
+ + {activeTab === 'network' && ( +
+

Graphe Réseau - Technologies et Compétences

+
+
+
+ Technologies Core +
+
+
+ Technologies Avancées +
+
+
+ Technologies Utilitaires +
+
+
+ Membres Équipe +
+
+
+
+ )} + + {activeTab === 'congestion' && ( +
+

Matrice de Congestion - Technologies Core

+
+
+ )} + + {activeTab === 'genesis' && ( +
+ {renderGenesisTeam()} +
+ )} +
+
+ ); +} +EOF