Ajout carte de profil cliquable pour les membres (graphe, matrice, equipe)
This commit is contained in:
@@ -69,7 +69,31 @@
|
|||||||
'.legend{display:flex;gap:20px;margin:20px 0;flex-wrap:wrap}' +
|
'.legend{display:flex;gap:20px;margin:20px 0;flex-wrap:wrap}' +
|
||||||
'.legend-item{display:flex;align-items:center;gap:8px}' +
|
'.legend-item{display:flex;align-items:center;gap:8px}' +
|
||||||
'.legend-color{width:20px;height:20px;border-radius:4px}' +
|
'.legend-color{width:20px;height:20px;border-radius:4px}' +
|
||||||
'.loading{text-align:center;padding:40px;color:#4ade80}';
|
'.loading{text-align:center;padding:40px;color:#4ade80}' +
|
||||||
|
'.clickable{cursor:pointer;transition:all 0.2s}' +
|
||||||
|
'.clickable:hover{transform:scale(1.02);box-shadow:0 4px 12px rgba(74,222,128,0.3)}' +
|
||||||
|
'.profile-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;z-index:9999;animation:fadeIn 0.2s}' +
|
||||||
|
'@keyframes fadeIn{from{opacity:0}to{opacity:1}}' +
|
||||||
|
'.profile-card{background:#1a4d3a;border:2px solid #4ade80;border-radius:12px;padding:25px;max-width:500px;width:90%;max-height:85vh;overflow-y:auto;position:relative;box-shadow:0 20px 60px rgba(0,0,0,0.5)}' +
|
||||||
|
'.profile-card .close-btn{position:absolute;top:15px;right:15px;background:none;border:none;color:#ff6b6b;font-size:28px;cursor:pointer;line-height:1}' +
|
||||||
|
'.profile-card .close-btn:hover{color:#ff4444}' +
|
||||||
|
'.profile-card h2{color:#4ade80;margin-bottom:5px;font-size:24px}' +
|
||||||
|
'.profile-card .role{color:#a0a0a0;font-size:14px;margin-bottom:15px}' +
|
||||||
|
'.profile-card .stats{display:flex;gap:15px;flex-wrap:wrap;margin-bottom:20px}' +
|
||||||
|
'.profile-card .stat{background:rgba(74,222,128,0.15);padding:8px 12px;border-radius:6px;font-size:13px}' +
|
||||||
|
'.profile-card .stat strong{color:#4ade80}' +
|
||||||
|
'.profile-card h3{color:#4ade80;font-size:16px;margin:15px 0 10px;border-bottom:1px solid rgba(74,222,128,0.3);padding-bottom:5px}' +
|
||||||
|
'.profile-card .skills{display:flex;flex-wrap:wrap;gap:8px}' +
|
||||||
|
'.profile-card .skill-tag{padding:5px 10px;border-radius:4px;font-size:12px;background:rgba(74,222,128,0.2);border:1px solid rgba(74,222,128,0.4)}' +
|
||||||
|
'.profile-card .skill-tag.expert{background:rgba(74,222,128,0.4);border-color:#4ade80}' +
|
||||||
|
'.profile-card .skill-tag.intermediate{background:rgba(57,151,212,0.3);border-color:#3997d4}' +
|
||||||
|
'.profile-card .skill-tag.beginner{background:rgba(245,179,54,0.2);border-color:#f5b336}' +
|
||||||
|
'.profile-card .interests{display:flex;flex-wrap:wrap;gap:6px}' +
|
||||||
|
'.profile-card .interest{background:rgba(136,255,136,0.15);padding:4px 8px;border-radius:4px;font-size:11px;color:#88ff88}' +
|
||||||
|
'.profile-card .projects{list-style:none;padding:0}' +
|
||||||
|
'.profile-card .projects li{padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.1);font-size:13px}' +
|
||||||
|
'.profile-card .projects li:last-child{border-bottom:none}' +
|
||||||
|
'.profile-card .bio{color:#c0c0c0;font-size:13px;line-height:1.5;margin-top:15px;font-style:italic}';
|
||||||
|
|
||||||
// HTML de la page
|
// HTML de la page
|
||||||
var html = '<div class="container">' +
|
var html = '<div class="container">' +
|
||||||
@@ -153,6 +177,9 @@
|
|||||||
var data = await response.json();
|
var data = await response.json();
|
||||||
console.log('EQUIPE: Donnees chargees:', Object.keys(data));
|
console.log('EQUIPE: Donnees chargees:', Object.keys(data));
|
||||||
|
|
||||||
|
// Stocker les profils pour acces global
|
||||||
|
window.__memberProfiles = data.memberProfiles || {};
|
||||||
|
|
||||||
// Initialiser les visualisations
|
// Initialiser les visualisations
|
||||||
initNetwork(data);
|
initNetwork(data);
|
||||||
initCongestion(data);
|
initCongestion(data);
|
||||||
@@ -229,6 +256,24 @@
|
|||||||
animationDuration: 800
|
animationDuration: 800
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ajouter evenement de clic sur les membres
|
||||||
|
window.networkCy.on('tap', 'node[type="member"]', function(evt) {
|
||||||
|
var node = evt.target;
|
||||||
|
var memberId = node.data('id').replace('member-', '');
|
||||||
|
if (window.__memberProfiles && window.__memberProfiles[memberId]) {
|
||||||
|
showMemberProfile(memberId, window.__memberProfiles[memberId]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Style curseur pour les membres
|
||||||
|
window.networkCy.on('mouseover', 'node[type="member"]', function() {
|
||||||
|
document.body.style.cursor = 'pointer';
|
||||||
|
});
|
||||||
|
window.networkCy.on('mouseout', 'node[type="member"]', function() {
|
||||||
|
document.body.style.cursor = 'default';
|
||||||
|
});
|
||||||
|
|
||||||
console.log('EQUIPE: Graphe reseau initialise');
|
console.log('EQUIPE: Graphe reseau initialise');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +297,7 @@
|
|||||||
scatter.push({
|
scatter.push({
|
||||||
value: [j, i],
|
value: [j, i],
|
||||||
member: m.fullName || m.member,
|
member: m.fullName || m.member,
|
||||||
|
memberId: m.member,
|
||||||
tech: row.technology,
|
tech: row.technology,
|
||||||
availability: m.availability
|
availability: m.availability
|
||||||
});
|
});
|
||||||
@@ -280,6 +326,17 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', function() { chart.resize(); });
|
window.addEventListener('resize', function() { chart.resize(); });
|
||||||
|
|
||||||
|
// Ajouter evenement de clic sur les points (membres)
|
||||||
|
chart.on('click', function(params) {
|
||||||
|
if (params.data && params.data.memberId) {
|
||||||
|
var memberId = params.data.memberId;
|
||||||
|
if (window.__memberProfiles && window.__memberProfiles[memberId]) {
|
||||||
|
showMemberProfile(memberId, window.__memberProfiles[memberId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
console.log('EQUIPE: Matrice de congestion initialisee');
|
console.log('EQUIPE: Matrice de congestion initialisee');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +358,7 @@
|
|||||||
|
|
||||||
if (g.team && g.team.length > 0) {
|
if (g.team && g.team.length > 0) {
|
||||||
g.team.forEach(function(m) {
|
g.team.forEach(function(m) {
|
||||||
h += '<div class="member-card">' +
|
h += '<div class="member-card clickable" data-member-id="' + m.member + '">' +
|
||||||
'<div class="member-header">' +
|
'<div class="member-header">' +
|
||||||
'<div><div class="member-name">' + (m.fullName || m.member) + '</div>' +
|
'<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 style="font-size:12px;color:#a0a0a0;margin-top:4px">' + (m.role || '') + ' - ' + m.seniority + ' - ' + m.coverage + ' technologie(s)</div>' +
|
||||||
@@ -329,9 +386,101 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('genesis-team').innerHTML = h;
|
document.getElementById('genesis-team').innerHTML = h;
|
||||||
|
|
||||||
|
// Ajouter les evenements de clic sur les cartes membres
|
||||||
|
document.querySelectorAll('.member-card.clickable').forEach(function(card) {
|
||||||
|
card.addEventListener('click', function() {
|
||||||
|
var memberId = this.getAttribute('data-member-id');
|
||||||
|
if (memberId && window.__memberProfiles && window.__memberProfiles[memberId]) {
|
||||||
|
showMemberProfile(memberId, window.__memberProfiles[memberId]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
console.log('EQUIPE: Equipe de genese initialisee');
|
console.log('EQUIPE: Equipe de genese initialisee');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Afficher la carte de profil d'un membre
|
||||||
|
function showMemberProfile(memberId, profile) {
|
||||||
|
if (!profile) {
|
||||||
|
console.warn('EQUIPE: Profil non trouve pour', memberId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generer le HTML des competences
|
||||||
|
var skillsHtml = '';
|
||||||
|
if (profile.skillsDetailed && profile.skillsDetailed.length > 0) {
|
||||||
|
skillsHtml = profile.skillsDetailed.map(function(s) {
|
||||||
|
var levelClass = s.level || 'beginner';
|
||||||
|
var yearsText = s.years ? ' (' + s.years + ' ans)' : '';
|
||||||
|
return '<span class="skill-tag ' + levelClass + '">' + s.name + yearsText + '</span>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generer le HTML des interets
|
||||||
|
var interestsHtml = '';
|
||||||
|
if (profile.interests && profile.interests.length > 0) {
|
||||||
|
interestsHtml = profile.interests.map(function(i) {
|
||||||
|
return '<span class="interest">' + i + '</span>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generer le HTML des projets
|
||||||
|
var projectsHtml = '';
|
||||||
|
if (profile.projects && profile.projects.length > 0) {
|
||||||
|
projectsHtml = '<ul class="projects">' + profile.projects.map(function(p) {
|
||||||
|
return '<li>' + p + '</li>';
|
||||||
|
}).join('') + '</ul>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generer le HTML des soft skills
|
||||||
|
var softSkillsHtml = '';
|
||||||
|
if (profile.softSkills && profile.softSkills.length > 0) {
|
||||||
|
softSkillsHtml = profile.softSkills.map(function(s) {
|
||||||
|
return '<span class="interest">' + s + '</span>';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
var modal = document.createElement('div');
|
||||||
|
modal.className = 'profile-modal';
|
||||||
|
modal.innerHTML =
|
||||||
|
'<div class="profile-card">' +
|
||||||
|
'<button class="close-btn">×</button>' +
|
||||||
|
'<h2>' + (profile.fullName || memberId) + '</h2>' +
|
||||||
|
'<p class="role">' + (profile.role || 'Membre de l\'equipe') + '</p>' +
|
||||||
|
'<div class="stats">' +
|
||||||
|
'<span class="stat"><strong>' + (profile.availability || 0) + '%</strong> disponibilite</span>' +
|
||||||
|
'<span class="stat"><strong>' + (profile.yearsExperience || 0) + '</strong> ans exp.</span>' +
|
||||||
|
'<span class="stat"><strong>' + (profile.seniorityLevel || 'beginner') + '</strong></span>' +
|
||||||
|
(profile.joinDate ? '<span class="stat">Depuis <strong>' + profile.joinDate + '</strong></span>' : '') +
|
||||||
|
'</div>' +
|
||||||
|
(skillsHtml ? '<h3>Competences techniques</h3><div class="skills">' + skillsHtml + '</div>' : '') +
|
||||||
|
(interestsHtml ? '<h3>Centres d\'interet</h3><div class="interests">' + interestsHtml + '</div>' : '') +
|
||||||
|
(softSkillsHtml ? '<h3>Soft Skills</h3><div class="interests">' + softSkillsHtml + '</div>' : '') +
|
||||||
|
(projectsHtml ? '<h3>Projets</h3>' + projectsHtml : '') +
|
||||||
|
(profile.bio ? '<p class="bio">' + profile.bio + '</p>' : '') +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
// Fermer au clic sur le fond ou le bouton
|
||||||
|
modal.addEventListener('click', function(e) {
|
||||||
|
if (e.target === modal || e.target.classList.contains('close-btn')) {
|
||||||
|
modal.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fermer avec Echap
|
||||||
|
var escHandler = function(e) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
modal.remove();
|
||||||
|
document.removeEventListener('keydown', escHandler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', escHandler);
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
console.log('EQUIPE: Profil affiche pour', memberId);
|
||||||
|
}
|
||||||
|
|
||||||
// Demarrer quand le DOM est pret
|
// Demarrer quand le DOM est pret
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', checkAndInitTeamPage);
|
document.addEventListener('DOMContentLoaded', checkAndInitTeamPage);
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ function extractYaml(yamlContent, key) {
|
|||||||
return match ? match[1].trim() : null;
|
return match ? match[1].trim() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extraire les compétences depuis le YAML
|
// Extraire les compétences depuis le YAML (avec details)
|
||||||
function extractSkills(yamlContent) {
|
function extractSkills(yamlContent) {
|
||||||
const skills = [];
|
const skills = [];
|
||||||
const lines = yamlContent.split('\n');
|
const lines = yamlContent.split('\n');
|
||||||
let inSkillsSection = false;
|
let inSkillsSection = false;
|
||||||
|
let currentSkill = null;
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
@@ -30,6 +31,7 @@ function extractSkills(yamlContent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inSkillsSection && line.match(/^\w+:/) && !line.match(/^\s+/)) {
|
if (inSkillsSection && line.match(/^\w+:/) && !line.match(/^\s+/)) {
|
||||||
|
if (currentSkill) skills.push(currentSkill);
|
||||||
inSkillsSection = false;
|
inSkillsSection = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -37,12 +39,60 @@ function extractSkills(yamlContent) {
|
|||||||
if (inSkillsSection) {
|
if (inSkillsSection) {
|
||||||
const nameMatch = line.match(/^\s+-\s+name:\s*["']?([^"'\n]+)["']?/);
|
const nameMatch = line.match(/^\s+-\s+name:\s*["']?([^"'\n]+)["']?/);
|
||||||
if (nameMatch) {
|
if (nameMatch) {
|
||||||
skills.push(nameMatch[1]);
|
if (currentSkill) skills.push(currentSkill);
|
||||||
|
currentSkill = { name: nameMatch[1], level: 'beginner', years: 0 };
|
||||||
|
}
|
||||||
|
const levelMatch = line.match(/^\s+level:\s*["']?([^"'\n]+)["']?/);
|
||||||
|
if (levelMatch && currentSkill) currentSkill.level = levelMatch[1];
|
||||||
|
const yearsMatch = line.match(/^\s+years:\s*(\d+)/);
|
||||||
|
if (yearsMatch && currentSkill) currentSkill.years = parseInt(yearsMatch[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentSkill) skills.push(currentSkill);
|
||||||
|
|
||||||
|
return skills;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraire une liste YAML simple (ex: interests, softSkills, projects)
|
||||||
|
function extractYamlList(yamlContent, key) {
|
||||||
|
const items = [];
|
||||||
|
const lines = yamlContent.split('\n');
|
||||||
|
let inSection = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
|
||||||
|
// Debut de section
|
||||||
|
if (line.match(new RegExp(`^${key}:\\s*$`))) {
|
||||||
|
inSection = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fin de section (nouvelle cle au niveau racine)
|
||||||
|
if (inSection && line.match(/^\w+:/) && !line.match(/^\s+/)) {
|
||||||
|
inSection = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraction des items
|
||||||
|
if (inSection) {
|
||||||
|
const itemMatch = line.match(/^\s+-\s*["']?([^"'\n]+)["']?/);
|
||||||
|
if (itemMatch) {
|
||||||
|
items.push(itemMatch[1].trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return skills;
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extraire la bio (contenu apres le front matter YAML)
|
||||||
|
function extractBio(content) {
|
||||||
|
const parts = content.split(/^---\n[\s\S]*?\n---/m);
|
||||||
|
if (parts.length > 1) {
|
||||||
|
return parts[1].trim().replace(/\n/g, ' ').substring(0, 500);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charger les technologies depuis les blips
|
// Charger les technologies depuis les blips
|
||||||
@@ -93,7 +143,7 @@ function loadTechnologies() {
|
|||||||
return technologies;
|
return technologies;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charger les membres de l'équipe
|
// Charger les membres de l'équipe (avec profils complets)
|
||||||
function loadTeamMembers() {
|
function loadTeamMembers() {
|
||||||
const teamDir = path.join(__dirname, '../docs/data/team');
|
const teamDir = path.join(__dirname, '../docs/data/team');
|
||||||
const members = [];
|
const members = [];
|
||||||
@@ -112,6 +162,8 @@ function loadTeamMembers() {
|
|||||||
if (!yamlMatch) continue;
|
if (!yamlMatch) continue;
|
||||||
|
|
||||||
const yaml = yamlMatch[1];
|
const yaml = yamlMatch[1];
|
||||||
|
const skillsData = extractSkills(yaml);
|
||||||
|
|
||||||
const member = {
|
const member = {
|
||||||
id: extractYaml(yaml, 'name') || file.replace('.md', ''),
|
id: extractYaml(yaml, 'name') || file.replace('.md', ''),
|
||||||
fullName: extractYaml(yaml, 'fullName') || extractYaml(yaml, 'name'),
|
fullName: extractYaml(yaml, 'fullName') || extractYaml(yaml, 'name'),
|
||||||
@@ -119,7 +171,13 @@ function loadTeamMembers() {
|
|||||||
availability: parseInt(extractYaml(yaml, 'availability') || '0'),
|
availability: parseInt(extractYaml(yaml, 'availability') || '0'),
|
||||||
seniorityLevel: extractYaml(yaml, 'seniorityLevel') || 'beginner',
|
seniorityLevel: extractYaml(yaml, 'seniorityLevel') || 'beginner',
|
||||||
yearsExperience: parseInt(extractYaml(yaml, 'yearsExperience') || '0'),
|
yearsExperience: parseInt(extractYaml(yaml, 'yearsExperience') || '0'),
|
||||||
skills: extractSkills(yaml)
|
joinDate: extractYaml(yaml, 'joinDate') || '',
|
||||||
|
skills: skillsData.map(s => typeof s === 'string' ? s : s.name),
|
||||||
|
skillsDetailed: skillsData,
|
||||||
|
interests: extractYamlList(yaml, 'interests'),
|
||||||
|
softSkills: extractYamlList(yaml, 'softSkills'),
|
||||||
|
projects: extractYamlList(yaml, 'projects'),
|
||||||
|
bio: extractBio(content)
|
||||||
};
|
};
|
||||||
|
|
||||||
members.push(member);
|
members.push(member);
|
||||||
@@ -342,10 +400,30 @@ function main() {
|
|||||||
console.log(`✅ ${technologies.length} technologies chargées`);
|
console.log(`✅ ${technologies.length} technologies chargées`);
|
||||||
console.log(`✅ ${members.length} membres chargés`);
|
console.log(`✅ ${members.length} membres chargés`);
|
||||||
|
|
||||||
|
// Creer un index des profils membres pour acces rapide
|
||||||
|
const memberProfiles = {};
|
||||||
|
members.forEach(m => {
|
||||||
|
memberProfiles[m.id] = {
|
||||||
|
id: m.id,
|
||||||
|
fullName: m.fullName,
|
||||||
|
role: m.role,
|
||||||
|
availability: m.availability,
|
||||||
|
seniorityLevel: m.seniorityLevel,
|
||||||
|
yearsExperience: m.yearsExperience,
|
||||||
|
joinDate: m.joinDate,
|
||||||
|
skillsDetailed: m.skillsDetailed,
|
||||||
|
interests: m.interests,
|
||||||
|
softSkills: m.softSkills,
|
||||||
|
projects: m.projects,
|
||||||
|
bio: m.bio
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
network: generateNetworkData(technologies, members),
|
network: generateNetworkData(technologies, members),
|
||||||
congestionMatrix: generateCongestionMatrix(technologies, members),
|
congestionMatrix: generateCongestionMatrix(technologies, members),
|
||||||
genesisTeam: generateGenesisTeam(technologies, members),
|
genesisTeam: generateGenesisTeam(technologies, members),
|
||||||
|
memberProfiles: memberProfiles,
|
||||||
technologies: technologies,
|
technologies: technologies,
|
||||||
members: members,
|
members: members,
|
||||||
generatedAt: new Date().toISOString()
|
generatedAt: new Date().toISOString()
|
||||||
|
|||||||
Reference in New Issue
Block a user