diff --git a/Dockerfile.business b/Dockerfile.business index 925d545..31820ec 100644 --- a/Dockerfile.business +++ b/Dockerfile.business @@ -100,480 +100,8 @@ RUN echo "📊 Comptage des fichiers .md dans .techradar/data/radar" && \ # CrĂ©er la page Next.js /team ET un fichier HTML statique /team/index.html # 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, 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 [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(() => { - // Charger les donnĂ©es Ă©quipe - loadTeamData(); - }, []); - - 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

- -
- -
- {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 +RUN mkdir -p .techradar/src/pages +COPY docker/team-page.tsx .techradar/src/pages/team.tsx # Script Python pour ajouter le lien Équipe dans Navigation.tsx (supprime TOUS les doublons) RUN cat > /tmp/add_team_link.py << 'PYEOF' diff --git a/docker/team-page.tsx b/docker/team-page.tsx new file mode 100644 index 0000000..b5f8069 --- /dev/null +++ b/docker/team-page.tsx @@ -0,0 +1,578 @@ +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 [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(() => { + // Charger les donnĂ©es Ă©quipe + loadTeamData(); + }, []); + + 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

+ +
+ +
+ {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()} +
+ )} +
+
+ ); +}