fix: Script team-block-script.js avec remplacement DOM
- Nouvelle approche: remplace le DOM apres chargement (defer-compatible) - Charge Cytoscape, ECharts dynamiquement - Initialise les 3 visualisations: graphe reseau, matrice congestion, equipe genese - Ajout de patch_document.py pour inclure le script dans _document.tsx
This commit is contained in:
@@ -99,6 +99,12 @@ RUN echo "📊 Comptage des fichiers .md dans .techradar/data/radar" && \
|
||||
RUN mkdir -p .techradar/src/pages
|
||||
COPY docker/team-page.tsx .techradar/src/pages/team.tsx
|
||||
|
||||
# Modifier _document.tsx pour charger team-block-script.js en premier (avant le rendu)
|
||||
COPY docker/patch_document.py /tmp/patch_document.py
|
||||
RUN python3 /tmp/patch_document.py && \
|
||||
echo "📄 _document.tsx apres modification:" && \
|
||||
cat .techradar/src/pages/_document.tsx
|
||||
|
||||
# Script Python pour ajouter le lien Équipe dans Navigation.tsx (supprime TOUS les doublons)
|
||||
COPY docker/add_team_link.py /tmp/add_team_link.py
|
||||
|
||||
|
||||
42
docker/patch_document.py
Normal file
42
docker/patch_document.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
# Script pour modifier _document.tsx et charger team-block-script.js en premier
|
||||
|
||||
import sys
|
||||
|
||||
doc_path = ".techradar/src/pages/_document.tsx"
|
||||
|
||||
try:
|
||||
with open(doc_path, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Ajouter l'import de Script si pas present
|
||||
if "import Script from 'next/script'" not in content and 'import Script from "next/script"' not in content:
|
||||
content = content.replace(
|
||||
'import { Head, Html, Main, NextScript } from "next/document";',
|
||||
'import { Head, Html, Main, NextScript } from "next/document";\nimport Script from "next/script";'
|
||||
)
|
||||
|
||||
# Ajouter le script dans <Head> avec strategy="beforeInteractive"
|
||||
if "team-block-script.js" not in content:
|
||||
# Trouver la fin de <Head /> et la remplacer par un <Head> avec contenu
|
||||
if "<Head />" in content:
|
||||
content = content.replace(
|
||||
"<Head />",
|
||||
'<Head>\n <Script src="/team-block-script.js" strategy="beforeInteractive" />\n </Head>'
|
||||
)
|
||||
elif "<Head>" in content and "</Head>" in content:
|
||||
# Ajouter avant </Head>
|
||||
content = content.replace(
|
||||
"</Head>",
|
||||
' <Script src="/team-block-script.js" strategy="beforeInteractive" />\n </Head>'
|
||||
)
|
||||
|
||||
with open(doc_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
print("_document.tsx modifie pour charger team-block-script.js en premier")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"Erreur: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SCRIPT ÉQUIPE - Affichage direct dans iframe
|
||||
// SCRIPT EQUIPE - Remplacement DOM apres chargement
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
@@ -6,198 +6,323 @@
|
||||
var isTeamRoute = path === '/team' || path === '/team/' || path.startsWith('/team/');
|
||||
|
||||
if (!isTeamRoute) {
|
||||
console.log('Page normale - team-block-script inactif');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('ÉQUIPE: Page team détectée');
|
||||
console.log('EQUIPE: Page team detectee, preparation du remplacement DOM');
|
||||
|
||||
// Éviter les boucles avec sessionStorage
|
||||
var loadKey = 'team_page_loaded_' + Date.now().toString().slice(0,-3);
|
||||
if (sessionStorage.getItem('team_loading')) {
|
||||
console.log('ÉQUIPE: Chargement déjà en cours, abandon');
|
||||
return;
|
||||
// Fonction pour charger un script externe
|
||||
function loadScript(src) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
sessionStorage.setItem('team_loading', 'true');
|
||||
|
||||
// Nettoyer après 2 secondes
|
||||
setTimeout(function() {
|
||||
sessionStorage.removeItem('team_loading');
|
||||
}, 2000);
|
||||
|
||||
// Stopper le chargement Next.js
|
||||
window.stop();
|
||||
// Fonction pour initialiser la page equipe
|
||||
async function initTeamPage() {
|
||||
console.log('EQUIPE: Initialisation de la page');
|
||||
|
||||
// CSS de la page
|
||||
var css = '*{margin:0;padding:0;box-sizing:border-box}' +
|
||||
'body{font-family:system-ui,sans-serif;background:#1a4d3a;color:#e0e0e0;padding:20px}' +
|
||||
'.container{max-width:1400px;margin:0 auto}' +
|
||||
'header{text-align:center;margin-bottom:30px;padding:20px;background:rgba(26,77,58,0.5);border-radius:8px}' +
|
||||
'h1{color:#4ade80;margin-bottom:10px}' +
|
||||
'.tabs{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap}' +
|
||||
'.tab-btn{padding:12px 24px;background:rgba(74,222,128,0.2);border:2px solid #4ade80;color:#4ade80;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600}' +
|
||||
'.tab-btn:hover{background:rgba(74,222,128,0.3)}' +
|
||||
'.tab-btn.active{background:#4ade80;color:#1a4d3a}' +
|
||||
'.tab-content{display:none;background:rgba(26,77,58,0.3);border-radius:8px;padding:20px;margin-bottom:20px}' +
|
||||
'.tab-content.active{display:block}' +
|
||||
'#network-graph{width:100%;height:700px;background:rgba(0,0,0,0.2);border-radius:8px;border:1px solid rgba(74,222,128,0.3)}' +
|
||||
'#congestion-matrix{width:100%;height:600px;background:rgba(0,0,0,0.2);border-radius:8px}' +
|
||||
'.genesis-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;margin-bottom:30px}' +
|
||||
'.stat-card{background:rgba(74,222,128,0.1);border:1px solid rgba(74,222,128,0.3);border-radius:6px;padding:15px}' +
|
||||
'.stat-value{font-size:32px;font-weight:bold;color:#4ade80}' +
|
||||
'.stat-label{font-size:14px;color:#a0a0a0;margin-top:5px}' +
|
||||
'.member-card{background:rgba(26,77,58,0.5);border:1px solid rgba(74,222,128,0.3);border-radius:6px;padding:15px;margin-bottom:15px}' +
|
||||
'.member-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}' +
|
||||
'.member-name{font-size:18px;font-weight:bold;color:#4ade80}' +
|
||||
'.member-avail{background:rgba(74,222,128,0.2);padding:5px 12px;border-radius:4px;font-size:14px}' +
|
||||
'.tech-list{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}' +
|
||||
'.tech-tag{background:rgba(74,222,128,0.2);border:1px solid rgba(74,222,128,0.4);padding:4px 10px;border-radius:4px;font-size:12px}' +
|
||||
'.warning-box{background:rgba(255,68,68,0.2);border:1px solid rgba(255,68,68,0.5);border-radius:6px;padding:15px;margin-top:20px}' +
|
||||
'.warning-title{color:#ff6b6b;font-weight:bold;margin-bottom:10px}' +
|
||||
'.uncovered{background:rgba(255,68,68,0.1);border-left:3px solid #ff6b6b;padding:10px;margin:8px 0;border-radius:4px}' +
|
||||
'.legend{display:flex;gap:20px;margin:20px 0;flex-wrap:wrap}' +
|
||||
'.legend-item{display:flex;align-items:center;gap:8px}' +
|
||||
'.legend-color{width:20px;height:20px;border-radius:4px}' +
|
||||
'.loading{text-align:center;padding:40px;color:#4ade80}';
|
||||
|
||||
// Créer la page directement
|
||||
var teamHTML = '<!DOCTYPE html><html lang="fr"><head>' +
|
||||
'<meta charset="UTF-8">' +
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1.0">' +
|
||||
'<title>Equipe - Laplank</title>' +
|
||||
'<script src="https://cdn.jsdelivr.net/npm/cytoscape@3.26.0/dist/cytoscape.min.js"><\/script>' +
|
||||
'<script src="https://cdn.jsdelivr.net/npm/cytoscape-cose-bilkent@4.1.0/cytoscape-cose-bilkent.min.js"><\/script>' +
|
||||
'<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"><\/script>' +
|
||||
'<style>' +
|
||||
'*{margin:0;padding:0;box-sizing:border-box}' +
|
||||
'body{font-family:system-ui,sans-serif;background:#1a4d3a;color:#e0e0e0;padding:20px}' +
|
||||
'.container{max-width:1400px;margin:0 auto}' +
|
||||
'header{text-align:center;margin-bottom:30px;padding:20px;background:rgba(26,77,58,0.5);border-radius:8px}' +
|
||||
'h1{color:#4ade80;margin-bottom:10px}' +
|
||||
'.tabs{display:flex;gap:10px;margin-bottom:20px;flex-wrap:wrap}' +
|
||||
'.tab-btn{padding:12px 24px;background:rgba(74,222,128,0.2);border:2px solid #4ade80;color:#4ade80;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600}' +
|
||||
'.tab-btn:hover{background:rgba(74,222,128,0.3)}' +
|
||||
'.tab-btn.active{background:#4ade80;color:#1a4d3a}' +
|
||||
'.tab-content{display:none;background:rgba(26,77,58,0.3);border-radius:8px;padding:20px;margin-bottom:20px}' +
|
||||
'.tab-content.active{display:block}' +
|
||||
'#network-graph{width:100%;height:700px;background:rgba(0,0,0,0.2);border-radius:8px;border:1px solid rgba(74,222,128,0.3)}' +
|
||||
'#congestion-matrix{width:100%;height:600px;background:rgba(0,0,0,0.2);border-radius:8px}' +
|
||||
'.genesis-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;margin-bottom:30px}' +
|
||||
'.stat-card{background:rgba(74,222,128,0.1);border:1px solid rgba(74,222,128,0.3);border-radius:6px;padding:15px}' +
|
||||
'.stat-value{font-size:32px;font-weight:bold;color:#4ade80}' +
|
||||
'.stat-label{font-size:14px;color:#a0a0a0;margin-top:5px}' +
|
||||
'.member-card{background:rgba(26,77,58,0.5);border:1px solid rgba(74,222,128,0.3);border-radius:6px;padding:15px;margin-bottom:15px}' +
|
||||
'.member-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}' +
|
||||
'.member-name{font-size:18px;font-weight:bold;color:#4ade80}' +
|
||||
'.member-avail{background:rgba(74,222,128,0.2);padding:5px 12px;border-radius:4px;font-size:14px}' +
|
||||
'.tech-list{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}' +
|
||||
'.tech-tag{background:rgba(74,222,128,0.2);border:1px solid rgba(74,222,128,0.4);padding:4px 10px;border-radius:4px;font-size:12px}' +
|
||||
'.warning-box{background:rgba(255,68,68,0.2);border:1px solid rgba(255,68,68,0.5);border-radius:6px;padding:15px;margin-top:20px}' +
|
||||
'.warning-title{color:#ff6b6b;font-weight:bold;margin-bottom:10px}' +
|
||||
'.uncovered{background:rgba(255,68,68,0.1);border-left:3px solid #ff6b6b;padding:10px;margin:8px 0;border-radius:4px}' +
|
||||
'.legend{display:flex;gap:20px;margin:20px 0;flex-wrap:wrap}' +
|
||||
'.legend-item{display:flex;align-items:center;gap:8px}' +
|
||||
'.legend-color{width:20px;height:20px;border-radius:4px}' +
|
||||
'.loading{text-align:center;padding:40px;color:#4ade80}' +
|
||||
'</style>' +
|
||||
'</head><body>' +
|
||||
'<div class="container">' +
|
||||
'<header>' +
|
||||
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">' +
|
||||
'<a href="/" style="color:#4ade80;text-decoration:none;font-size:18px;font-weight:bold">← Retour au Radar</a>' +
|
||||
'<div></div>' +
|
||||
'</div>' +
|
||||
'<h1>Equipe & Technologies</h1>' +
|
||||
'<p>Visualisation des competences et identification de l\'equipe de genese MVP</p>' +
|
||||
'</header>' +
|
||||
'<div class="tabs">' +
|
||||
'<button class="tab-btn active" onclick="showTab(\'network\')">Graphe Reseau</button>' +
|
||||
'<button class="tab-btn" onclick="showTab(\'congestion\')">Matrice Congestion</button>' +
|
||||
'<button class="tab-btn" onclick="showTab(\'genesis\')">Equipe Genese MVP</button>' +
|
||||
'</div>' +
|
||||
'<div id="network-tab" class="tab-content active">' +
|
||||
'<div class="legend">' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#4ade80"></div><span>Adopt</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#3997d4"></div><span>Trial</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#f5b336"></div><span>Assess</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#e5695e"></div><span>Hold</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#88ff88"></div><span>Membres</span></div>' +
|
||||
'</div>' +
|
||||
'<div id="network-graph"></div>' +
|
||||
'</div>' +
|
||||
'<div id="congestion-tab" class="tab-content">' +
|
||||
'<h2 style="margin-bottom:20px">Matrice de Congestion - Technologies Adopt</h2>' +
|
||||
'<div id="congestion-matrix"></div>' +
|
||||
'</div>' +
|
||||
'<div id="genesis-tab" class="tab-content">' +
|
||||
'<div id="genesis-team"><div class="loading">Chargement des donnees...</div></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<script>' +
|
||||
'var data=null,networkCy=null,congestionChart=null;' +
|
||||
'async function loadData(){' +
|
||||
'try{' +
|
||||
'var r=await fetch("/team-visualization-data.json");' +
|
||||
'if(!r.ok)throw new Error("HTTP "+r.status);' +
|
||||
'data=await r.json();' +
|
||||
'console.log("Donnees chargees:",Object.keys(data));' +
|
||||
'initVis();' +
|
||||
'}catch(e){' +
|
||||
'console.error("Erreur:",e);' +
|
||||
'var msg="<div style=\\"padding:20px;background:rgba(255,152,0,0.1);border:1px solid #ff9800;border-radius:8px;margin:20px\\"><h3 style=\\"color:#ff9800\\">Chargement...</h3><p>Erreur: "+e.message+"</p></div>";' +
|
||||
'document.getElementById("network-graph").innerHTML=msg;' +
|
||||
'document.getElementById("congestion-matrix").innerHTML=msg;' +
|
||||
'document.getElementById("genesis-team").innerHTML=msg;' +
|
||||
'}' +
|
||||
'}' +
|
||||
'function initVis(){initNetwork();initCongestion();initGenesis()}' +
|
||||
'function initNetwork(){' +
|
||||
'if(!data||!data.network)return;' +
|
||||
'networkCy=cytoscape({' +
|
||||
'container:document.getElementById("network-graph"),' +
|
||||
'elements:data.network,' +
|
||||
'style:[' +
|
||||
'{selector:"node[type=\\"technology\\"]",style:{"background-color":"data(color)","label":"data(label)","width":function(e){return 30+(e.data("coverage")||0)*8},"height":function(e){return 30+(e.data("coverage")||0)*8},"color":"#fff","font-size":"12px","text-outline-width":2,"text-outline-color":"#000","text-wrap":"wrap","text-max-width":100}},' +
|
||||
'{selector:"node[type=\\"member\\"]",style:{"background-color":"#88ff88","label":"data(label)","width":function(e){return 25+(e.data("availability")||0)/3},"height":function(e){return 25+(e.data("availability")||0)/3},"color":"#1a4d3a","font-size":"11px","font-weight":"bold","shape":"ellipse"}},' +
|
||||
'{selector:"edge",style:{"width":function(e){return 1+(e.data("weight")||0.5)},"line-color":"#999","opacity":0.6,"curve-style":"bezier"}}' +
|
||||
'],' +
|
||||
'layout:{name:"cose-bilkent",nodeDimensionsIncludeLabels:true,idealEdgeLength:100,nodeRepulsion:4500,nestingFactor:0.1,gravity:0.25,numIter:2500,tile:true,animate:true,animationDuration:1000}' +
|
||||
'});' +
|
||||
'}' +
|
||||
'function initCongestion(){' +
|
||||
'if(!data||!data.congestionMatrix)return;' +
|
||||
'var c=echarts.init(document.getElementById("congestion-matrix"));' +
|
||||
'congestionChart=c;' +
|
||||
'var techs=data.congestionMatrix.map(function(r){return r.technology});' +
|
||||
'var members=data.congestionMatrix[0]?data.congestionMatrix[0].members.map(function(m){return m.fullName||m.member}):[];' +
|
||||
'var scatter=[];' +
|
||||
'data.congestionMatrix.forEach(function(row,i){row.members.forEach(function(m,j){if(m.hasSkill)scatter.push({value:[j,i],member:m.fullName||m.member,tech:row.technology,availability:m.availability})})});' +
|
||||
'c.setOption({' +
|
||||
'title:{text:"Disponibilite des membres sur les technologies Adopt",left:"center",textStyle:{color:"#e0e0e0"}},' +
|
||||
'tooltip:{formatter:function(p){return p.data.member+"<br/>Tech: "+p.data.tech+"<br/>Dispo: "+p.data.availability+"%"}},' +
|
||||
'grid:{height:"60%",top:"15%"},' +
|
||||
'xAxis:{type:"category",data:members,axisLabel:{rotate:45,color:"#e0e0e0"},axisLine:{lineStyle:{color:"#4ade80"}}},' +
|
||||
'yAxis:{type:"category",data:techs,axisLabel:{color:"#e0e0e0"},axisLine:{lineStyle:{color:"#4ade80"}}},' +
|
||||
'visualMap:{min:0,max:100,calculable:true,orient:"horizontal",left:"center",bottom:"5%",inRange:{color:["#1a4d3a","#4ade80","#86efac"]},textStyle:{color:"#e0e0e0"}},' +
|
||||
'series:[{type:"scatter",data:scatter,symbolSize:function(d){return 15+(d[2]||0)/2},itemStyle:{color:"#4ade80",borderColor:"#1a4d3a",borderWidth:2},label:{show:true,formatter:function(p){return p.data.availability+"%"},color:"#1a4d3a"}}]' +
|
||||
'});' +
|
||||
'window.addEventListener("resize",function(){c.resize()});' +
|
||||
'}' +
|
||||
'function initGenesis(){' +
|
||||
'if(!data||!data.genesisTeam)return;' +
|
||||
'var g=data.genesisTeam;' +
|
||||
'var h="<div class=\\"genesis-stats\\">"' +
|
||||
'+"<div class=\\"stat-card\\"><div class=\\"stat-value\\">"+g.totalMembers+"</div><div class=\\"stat-label\\">Membres selectionnes</div></div>"' +
|
||||
'+"<div class=\\"stat-card\\"><div class=\\"stat-value\\">"+g.totalCapacity+"%</div><div class=\\"stat-label\\">Capacite totale</div></div>"' +
|
||||
'+"<div class=\\"stat-card\\"><div class=\\"stat-value\\">"+g.averageAvailability+"%</div><div class=\\"stat-label\\">Disponibilite moyenne</div></div>"' +
|
||||
'+"<div class=\\"stat-card\\"><div class=\\"stat-value\\">"+g.coveredTechnologies+"/"+g.totalCoreTechnologies+"</div><div class=\\"stat-label\\">Technologies couvertes</div></div>"' +
|
||||
'+"</div>"' +
|
||||
'+"<h2 style=\\"margin-bottom:20px;color:#4ade80\\">Membres de l\'equipe de genese</h2>";' +
|
||||
'if(g.team.length>0){g.team.forEach(function(m){' +
|
||||
'h+="<div class=\\"member-card\\">"' +
|
||||
'+"<div class=\\"member-header\\">"' +
|
||||
'+"<div><div class=\\"member-name\\">"+(m.fullName||m.member)+"</div>"' +
|
||||
'+"<div style=\\"font-size:12px;color:#a0a0a0;margin-top:4px\\">"+(m.role||"")+" - "+m.seniority+" - "+m.coverage+" technologie(s)</div>"' +
|
||||
'+"</div>"' +
|
||||
'+"<div class=\\"member-avail\\">"+m.availability+"% dispo</div>"' +
|
||||
'+"</div>"' +
|
||||
'+"<div class=\\"tech-list\\">";' +
|
||||
'm.technologies.forEach(function(t){h+="<span class=\\"tech-tag\\">"+t.title+"</span>"});' +
|
||||
'h+="</div></div>"' +
|
||||
'})}else{h+="<p style=\\"color:#a0a0a0\\">Aucun membre ne repond aux criteres.</p>"}' +
|
||||
'if(g.uncoveredTechnologies.length>0){' +
|
||||
'h+="<div class=\\"warning-box\\"><div class=\\"warning-title\\">Technologies Adopt non couvertes</div><p style=\\"margin-bottom:10px\\">Ces technologies critiques ne sont pas maitrisees :</p>";' +
|
||||
'g.uncoveredTechnologies.forEach(function(t){' +
|
||||
'h+="<div class=\\"uncovered\\"><strong>"+t.title+"</strong><div style=\\"font-size:12px;color:#a0a0a0;margin-top:4px\\">Impact: "+t.businessImpact+" - Gap: "+t.skillGap+" - Couverture: "+t.teamCoverage+" personne(s)</div></div>"' +
|
||||
'});' +
|
||||
'h+="</div>"' +
|
||||
'}' +
|
||||
'document.getElementById("genesis-team").innerHTML=h;' +
|
||||
'}' +
|
||||
'function showTab(n){' +
|
||||
'document.querySelectorAll(".tab-content").forEach(function(t){t.classList.remove("active")});' +
|
||||
'document.querySelectorAll(".tab-btn").forEach(function(b){b.classList.remove("active")});' +
|
||||
'document.getElementById(n+"-tab").classList.add("active");' +
|
||||
'event.target.classList.add("active");' +
|
||||
'if(n==="congestion"&&congestionChart)setTimeout(function(){congestionChart.resize()},100);' +
|
||||
'if(n==="network"&&networkCy)setTimeout(function(){networkCy.resize()},100);' +
|
||||
'}' +
|
||||
'loadData();' +
|
||||
'<\/script>' +
|
||||
'</body></html>';
|
||||
// HTML de la page
|
||||
var html = '<div class="container">' +
|
||||
'<header>' +
|
||||
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">' +
|
||||
'<a href="/" style="color:#4ade80;text-decoration:none;font-size:18px;font-weight:bold">← Retour au Radar</a>' +
|
||||
'<div></div>' +
|
||||
'</div>' +
|
||||
'<h1>Equipe & Technologies</h1>' +
|
||||
'<p>Visualisation des competences et identification de l\'equipe de genese MVP</p>' +
|
||||
'</header>' +
|
||||
'<div class="tabs">' +
|
||||
'<button class="tab-btn active" data-tab="network">Graphe Reseau</button>' +
|
||||
'<button class="tab-btn" data-tab="congestion">Matrice Congestion</button>' +
|
||||
'<button class="tab-btn" data-tab="genesis">Equipe Genese MVP</button>' +
|
||||
'</div>' +
|
||||
'<div id="network-tab" class="tab-content active">' +
|
||||
'<div class="legend">' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#4ade80"></div><span>Adopt</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#3997d4"></div><span>Trial</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#f5b336"></div><span>Assess</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#e5695e"></div><span>Hold</span></div>' +
|
||||
'<div class="legend-item"><div class="legend-color" style="background:#88ff88"></div><span>Membres</span></div>' +
|
||||
'</div>' +
|
||||
'<div id="network-graph"><div class="loading">Chargement du graphe...</div></div>' +
|
||||
'</div>' +
|
||||
'<div id="congestion-tab" class="tab-content">' +
|
||||
'<h2 style="margin-bottom:20px">Matrice de Congestion - Technologies Adopt</h2>' +
|
||||
'<div id="congestion-matrix"><div class="loading">Chargement de la matrice...</div></div>' +
|
||||
'</div>' +
|
||||
'<div id="genesis-tab" class="tab-content">' +
|
||||
'<div id="genesis-team"><div class="loading">Chargement des donnees...</div></div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
// Remplacer le document
|
||||
document.open();
|
||||
document.write(teamHTML);
|
||||
document.close();
|
||||
|
||||
console.log('ÉQUIPE: Page team injectée');
|
||||
// Injecter le CSS
|
||||
var styleEl = document.createElement('style');
|
||||
styleEl.textContent = css;
|
||||
document.head.appendChild(styleEl);
|
||||
|
||||
// Modifier le titre
|
||||
document.title = 'Equipe & Technologies - Laplank';
|
||||
|
||||
// Remplacer le contenu du body
|
||||
document.body.innerHTML = html;
|
||||
document.body.style.cssText = 'font-family:system-ui,sans-serif;background:#1a4d3a;color:#e0e0e0;padding:20px;margin:0';
|
||||
|
||||
// Ajouter les gestionnaires d'evenements pour les onglets
|
||||
document.querySelectorAll('.tab-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var tabName = this.getAttribute('data-tab');
|
||||
document.querySelectorAll('.tab-content').forEach(function(t) { t.classList.remove('active'); });
|
||||
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
document.getElementById(tabName + '-tab').classList.add('active');
|
||||
this.classList.add('active');
|
||||
|
||||
// Redimensionner les graphiques
|
||||
if (tabName === 'congestion' && window.congestionChart) {
|
||||
setTimeout(function() { window.congestionChart.resize(); }, 100);
|
||||
}
|
||||
if (tabName === 'network' && window.networkCy) {
|
||||
setTimeout(function() { window.networkCy.resize(); }, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('EQUIPE: DOM remplace, chargement des bibliotheques...');
|
||||
|
||||
// Charger les bibliotheques externes
|
||||
try {
|
||||
await loadScript('https://cdn.jsdelivr.net/npm/cytoscape@3.26.0/dist/cytoscape.min.js');
|
||||
console.log('EQUIPE: Cytoscape charge');
|
||||
await loadScript('https://cdn.jsdelivr.net/npm/cytoscape-cose-bilkent@4.1.0/cytoscape-cose-bilkent.min.js');
|
||||
console.log('EQUIPE: Cytoscape-cose-bilkent charge');
|
||||
await loadScript('https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js');
|
||||
console.log('EQUIPE: ECharts charge');
|
||||
|
||||
// Charger les donnees
|
||||
var response = await fetch('/team-visualization-data.json');
|
||||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||
var data = await response.json();
|
||||
console.log('EQUIPE: Donnees chargees:', Object.keys(data));
|
||||
|
||||
// Initialiser les visualisations
|
||||
initNetwork(data);
|
||||
initCongestion(data);
|
||||
initGenesis(data);
|
||||
|
||||
} catch (e) {
|
||||
console.error('EQUIPE: Erreur:', e);
|
||||
var errorMsg = '<div style="padding:20px;background:rgba(255,152,0,0.2);border:1px solid #ff9800;border-radius:8px"><h3 style="color:#ff9800">Erreur de chargement</h3><p>' + e.message + '</p></div>';
|
||||
document.getElementById('network-graph').innerHTML = errorMsg;
|
||||
document.getElementById('congestion-matrix').innerHTML = errorMsg;
|
||||
document.getElementById('genesis-team').innerHTML = errorMsg;
|
||||
}
|
||||
}
|
||||
|
||||
// Graphe reseau avec Cytoscape
|
||||
function initNetwork(data) {
|
||||
if (!data || !data.network) {
|
||||
document.getElementById('network-graph').innerHTML = '<div class="loading">Pas de donnees reseau</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
window.networkCy = cytoscape({
|
||||
container: document.getElementById('network-graph'),
|
||||
elements: data.network,
|
||||
style: [
|
||||
{
|
||||
selector: 'node[type="technology"]',
|
||||
style: {
|
||||
'background-color': 'data(color)',
|
||||
'label': 'data(label)',
|
||||
'width': function(e) { return 30 + (e.data('coverage') || 0) * 8; },
|
||||
'height': function(e) { return 30 + (e.data('coverage') || 0) * 8; },
|
||||
'color': '#fff',
|
||||
'font-size': '12px',
|
||||
'text-outline-width': 2,
|
||||
'text-outline-color': '#000',
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': 100
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'node[type="member"]',
|
||||
style: {
|
||||
'background-color': '#88ff88',
|
||||
'label': 'data(label)',
|
||||
'width': function(e) { return 25 + (e.data('availability') || 0) / 3; },
|
||||
'height': function(e) { return 25 + (e.data('availability') || 0) / 3; },
|
||||
'color': '#1a4d3a',
|
||||
'font-size': '11px',
|
||||
'font-weight': 'bold',
|
||||
'shape': 'ellipse'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': function(e) { return 1 + (e.data('weight') || 0.5); },
|
||||
'line-color': '#999',
|
||||
'opacity': 0.6,
|
||||
'curve-style': 'bezier'
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
name: 'cose-bilkent',
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
idealEdgeLength: 100,
|
||||
nodeRepulsion: 4500,
|
||||
nestingFactor: 0.1,
|
||||
gravity: 0.25,
|
||||
numIter: 2500,
|
||||
tile: true,
|
||||
animate: true,
|
||||
animationDuration: 1000
|
||||
}
|
||||
});
|
||||
console.log('EQUIPE: Graphe reseau initialise');
|
||||
}
|
||||
|
||||
// Matrice de congestion avec ECharts
|
||||
function initCongestion(data) {
|
||||
if (!data || !data.congestionMatrix || data.congestionMatrix.length === 0) {
|
||||
document.getElementById('congestion-matrix').innerHTML = '<div class="loading">Pas de donnees matrice</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
var chart = echarts.init(document.getElementById('congestion-matrix'));
|
||||
window.congestionChart = chart;
|
||||
|
||||
var techs = data.congestionMatrix.map(function(r) { return r.technology; });
|
||||
var members = data.congestionMatrix[0] ? data.congestionMatrix[0].members.map(function(m) { return m.fullName || m.member; }) : [];
|
||||
var scatter = [];
|
||||
|
||||
data.congestionMatrix.forEach(function(row, i) {
|
||||
row.members.forEach(function(m, j) {
|
||||
if (m.hasSkill) {
|
||||
scatter.push({
|
||||
value: [j, i],
|
||||
member: m.fullName || m.member,
|
||||
tech: row.technology,
|
||||
availability: m.availability
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
chart.setOption({
|
||||
title: { text: 'Disponibilite des membres sur les technologies Adopt', left: 'center', textStyle: { color: '#e0e0e0' } },
|
||||
tooltip: {
|
||||
formatter: function(p) {
|
||||
return p.data.member + '<br/>Tech: ' + p.data.tech + '<br/>Dispo: ' + p.data.availability + '%';
|
||||
}
|
||||
},
|
||||
grid: { height: '60%', top: '15%' },
|
||||
xAxis: { type: 'category', data: members, axisLabel: { rotate: 45, color: '#e0e0e0' }, axisLine: { lineStyle: { color: '#4ade80' } } },
|
||||
yAxis: { type: 'category', data: techs, axisLabel: { color: '#e0e0e0' }, axisLine: { lineStyle: { color: '#4ade80' } } },
|
||||
visualMap: { min: 0, max: 100, calculable: true, orient: 'horizontal', left: 'center', bottom: '5%', inRange: { color: ['#1a4d3a', '#4ade80', '#86efac'] }, textStyle: { color: '#e0e0e0' } },
|
||||
series: [{
|
||||
type: 'scatter',
|
||||
data: scatter,
|
||||
symbolSize: function(d) { return 15 + (d[2] || 0) / 2; },
|
||||
itemStyle: { color: '#4ade80', borderColor: '#1a4d3a', borderWidth: 2 },
|
||||
label: { show: true, formatter: function(p) { return p.data.availability + '%'; }, color: '#1a4d3a' }
|
||||
}]
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() { chart.resize(); });
|
||||
console.log('EQUIPE: Matrice de congestion initialisee');
|
||||
}
|
||||
|
||||
// Equipe de genese
|
||||
function initGenesis(data) {
|
||||
if (!data || !data.genesisTeam) {
|
||||
document.getElementById('genesis-team').innerHTML = '<div class="loading">Pas de donnees equipe</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
var g = data.genesisTeam;
|
||||
var h = '<div class="genesis-stats">' +
|
||||
'<div class="stat-card"><div class="stat-value">' + g.totalMembers + '</div><div class="stat-label">Membres selectionnes</div></div>' +
|
||||
'<div class="stat-card"><div class="stat-value">' + g.totalCapacity + '%</div><div class="stat-label">Capacite totale</div></div>' +
|
||||
'<div class="stat-card"><div class="stat-value">' + g.averageAvailability + '%</div><div class="stat-label">Disponibilite moyenne</div></div>' +
|
||||
'<div class="stat-card"><div class="stat-value">' + g.coveredTechnologies + '/' + g.totalCoreTechnologies + '</div><div class="stat-label">Technologies couvertes</div></div>' +
|
||||
'</div>' +
|
||||
'<h2 style="margin-bottom:20px;color:#4ade80">Membres de l\'equipe de genese</h2>';
|
||||
|
||||
if (g.team && g.team.length > 0) {
|
||||
g.team.forEach(function(m) {
|
||||
h += '<div class="member-card">' +
|
||||
'<div class="member-header">' +
|
||||
'<div><div class="member-name">' + (m.fullName || m.member) + '</div>' +
|
||||
'<div style="font-size:12px;color:#a0a0a0;margin-top:4px">' + (m.role || '') + ' - ' + m.seniority + ' - ' + m.coverage + ' technologie(s)</div>' +
|
||||
'</div>' +
|
||||
'<div class="member-avail">' + m.availability + '% dispo</div>' +
|
||||
'</div>' +
|
||||
'<div class="tech-list">';
|
||||
if (m.technologies) {
|
||||
m.technologies.forEach(function(t) {
|
||||
h += '<span class="tech-tag">' + t.title + '</span>';
|
||||
});
|
||||
}
|
||||
h += '</div></div>';
|
||||
});
|
||||
} else {
|
||||
h += '<p style="color:#a0a0a0">Aucun membre ne repond aux criteres.</p>';
|
||||
}
|
||||
|
||||
if (g.uncoveredTechnologies && g.uncoveredTechnologies.length > 0) {
|
||||
h += '<div class="warning-box"><div class="warning-title">Technologies Adopt non couvertes</div><p style="margin-bottom:10px">Ces technologies critiques ne sont pas maitrisees :</p>';
|
||||
g.uncoveredTechnologies.forEach(function(t) {
|
||||
h += '<div class="uncovered"><strong>' + t.title + '</strong><div style="font-size:12px;color:#a0a0a0;margin-top:4px">Impact: ' + t.businessImpact + ' - Gap: ' + t.skillGap + ' - Couverture: ' + t.teamCoverage + ' personne(s)</div></div>';
|
||||
});
|
||||
h += '</div>';
|
||||
}
|
||||
|
||||
document.getElementById('genesis-team').innerHTML = h;
|
||||
console.log('EQUIPE: Equipe de genese initialisee');
|
||||
}
|
||||
|
||||
// Demarrer quand le DOM est pret
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initTeamPage);
|
||||
} else {
|
||||
initTeamPage();
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user