Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>OpEx Maturity Assessment Tool</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { font-family: 'Inter', sans-serif; background-color: #f8fafc; } | |
| .glass-card { background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); border: 1px solid rgba(226, 232, 240, 1); } | |
| .level-btn { transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } | |
| /* Accordion transition */ | |
| .accordion-content { | |
| max-height: 0; | |
| overflow: hidden; | |
| transition: max-height 0.3s ease-out, padding 0.3s ease; | |
| } | |
| .accordion-content.open { | |
| max-height: 2000px; /* Large enough for the table */ | |
| transition: max-height 0.5s ease-in; | |
| } | |
| .level-0 { background-color: #fee2e2; border-color: #ef4444; color: #b91c1c; } | |
| .level-1 { background-color: #fef9c3; border-color: #eab308; color: #a16207; } | |
| .level-2 { background-color: #fef3c7; border-color: #f59e0b; color: #b45309; } | |
| .level-3 { background-color: #dcfce7; border-color: #22c55e; color: #15803d; } | |
| .level-4 { background-color: #dcfce7; border-color: #16a34a; color: #166534; font-weight: bold; } | |
| .active-0 { background-color: #ef4444 ; color: white ; box-shadow: 0 4px 6px -1px rgba(239, 68, 68, 0.4); } | |
| .active-1 { background-color: #eab308 ; color: white ; box-shadow: 0 4px 6px -1px rgba(234, 179, 8, 0.4); } | |
| .active-2 { background-color: #f59e0b ; color: white ; box-shadow: 0 4px 6px -1px rgba(245, 158, 11, 0.4); } | |
| .active-3 { background-color: #22c55e ; color: white ; box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.4); } | |
| .active-4 { background-color: #16a34a ; color: white ; box-shadow: 0 4px 6px -1px rgba(22, 163, 74, 0.4); } | |
| @media print { | |
| .no-print { display: none; } | |
| .accordion-content { max-height: none ; display: block ; } | |
| body { background: white; padding: 0; } | |
| .glass-card { border: 1px solid #ddd; margin-bottom: 20px; page-break-inside: avoid; } | |
| } | |
| </style> | |
| </head> | |
| <body class="p-4 md:p-8"> | |
| <div id="app" class="max-w-7xl mx-auto"> | |
| <!-- Header --> | |
| <header class="flex flex-col md:flex-row justify-between items-center mb-10 gap-4 no-print"> | |
| <div> | |
| <h1 class="text-3xl font-bold text-slate-900 tracking-tight">Grille de Maturité OpEx</h1> | |
| <p class="text-slate-500 italic">Analyse interactive des écarts de performance</p> | |
| </div> | |
| <div class="flex gap-3"> | |
| <div id="global-progress" class="hidden md:flex flex-col items-end mr-4"> | |
| <span class="text-xs font-bold text-slate-400 uppercase">Complétion Totale</span> | |
| <div class="w-48 h-2 bg-slate-200 rounded-full mt-1 overflow-hidden"> | |
| <div id="global-progress-bar" class="h-full bg-indigo-500 transition-all duration-500" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <button onclick="exportToPDF()" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2.5 rounded-xl font-semibold shadow-lg shadow-indigo-200 transition-all flex items-center gap-2"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg> | |
| Exporter PDF | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Charts Dashboard --> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12 no-print"> | |
| <div class="glass-card p-6 rounded-2xl shadow-sm"> | |
| <h3 class="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="text-indigo-500" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2v20"/><path d="M2 12h20"/></svg> | |
| Profil de Maturité | |
| </h3> | |
| <div class="relative h-[320px]"> | |
| <canvas id="radarChart"></canvas> | |
| </div> | |
| </div> | |
| <div class="glass-card p-6 rounded-2xl shadow-sm"> | |
| <h3 class="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="text-indigo-500" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg> | |
| Analyse par Dimension (%) | |
| </h3> | |
| <div class="relative h-[320px]"> | |
| <canvas id="barChart"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Assessment Table (Accordion) --> | |
| <div class="space-y-4 pb-20" id="assessment-content"> | |
| <!-- Dynamic Content --> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="fixed bottom-8 right-8 bg-slate-900 text-white px-6 py-3 rounded-xl shadow-2xl transform translate-y-20 opacity-0 transition-all duration-300 z-50"> | |
| Génération du rapport en cours... | |
| </div> | |
| <script> | |
| const dataStructure = [ | |
| { id: "org", title: "Organisation & Leadership", questions: ["Les rôles et responsabilités OpEx sont-ils clairement définis ?", "Engagement régulier des leaders au Gemba (terrain) ?", "Comportements de leadership explicitement définis ?", "Forte collaboration transversale dans les projets ?", "Reconnaissance systématique des comportements OpEx ?"] }, | |
| { id: "strat", title: "Stratégie & Hoshin", questions: ["Vision OpEx claire et 'étoile polaire' communiquée ?", "Déploiement des objectifs 3-5 ans vers les équipes ?", "Processus Hoshin Kanri avec boucles de feedback ?", "Priorisation du portefeuille selon la stratégie ?", "Ajustement régulier face aux changements du marché ?"] }, | |
| { id: "loss", title: "Intelligence des Pertes", questions: ["Suivi systématique des pertes (OEE, temps, qualité) ?", "Analyse méthodique des causes racines des pertes ?", "Système de prévention de la récurrence des pertes ?", "Données de pertes orientant les priorités d'amélioration ?", "Transparence des données avec les équipes de terrain ?"] }, | |
| { id: "proc", title: "Processus & Standards", questions: ["VSM (Cartographie de flux) pour les chaînes clés ?", "Standards de travail (SOP, Takt, WIP) appliqués ?", "Management visuel déployé dans les zones clés ?", "Résolution de problèmes suite aux écarts standards ?", "Propriétaires de processus garants de la tenue ?"] }, | |
| { id: "perf", title: "Gestion de la Performance", questions: ["Indicateurs SQDCM définis à chaque niveau ?", "Rituels de performance (AIC) structurés et visuels ?", "Cascadage et alignement des KPI (Top-Down) ?", "Analyse de causes racines pour tout écart de KPI ?", "Données fiables et accessibles en temps réel ?"] }, | |
| { id: "ci", title: "Amélioration Continue", questions: ["Système formel CI (Lean, TPM, Six Sigma) ?", "Formation et usage quotidien des outils de base ?", "Pipeline de projets structuré (Impact vs Effort) ?", "Améliorations standardisées et auditées ?", "Capitalisation et réplication des bonnes pratiques ?"] }, | |
| { id: "people", title: "Engagement & Capacité", questions: ["Système Kaizen/Suggestions avec feedback ?", "Matrices de compétences et plans de formation ?", "Implication des opérateurs dans les standards ?", "Sondages culturels réguliers avec plans d'actions ?", "Parcours de carrière visibles pour l'expertise OpEx ?"] }, | |
| { id: "cust", title: "Satisfaction Client", questions: ["Collecte systématique de la voix du client ?", "Indicateurs client alignés aux KPI internes ?", "Exigences clients traduites en processus ?", "Système de résolution rapide des plaintes ?", "Boucle de feedback fermée avec le client ?"] }, | |
| { id: "results", title: "Résultats Business", questions: ["Cibles financières et opérationnelles atteintes ?", "Lien direct entre gains OpEx et P&L ?", "Tendances durables sur productivité/coûts/qualité ?", "Amélioration des parts de marché/compétitivité ?", "Business model profitable et scalable ?"] } | |
| ]; | |
| const scores = {}; | |
| const completion = {}; | |
| dataStructure.forEach(p => { | |
| scores[p.id] = p.questions.map(() => 0); | |
| completion[p.id] = 0; | |
| }); | |
| let radarChart, barChart; | |
| function initApp() { | |
| renderAssessment(); | |
| initCharts(); | |
| // Open first accordion by default | |
| toggleAccordion(dataStructure[0].id); | |
| } | |
| function renderAssessment() { | |
| const container = document.getElementById('assessment-content'); | |
| container.innerHTML = dataStructure.map(pillar => ` | |
| <div class="glass-card rounded-2xl overflow-hidden shadow-sm border border-slate-200 bg-white" id="card-${pillar.id}"> | |
| <!-- Accordion Header --> | |
| <div onclick="toggleAccordion('${pillar.id}')" class="cursor-pointer bg-white px-6 py-4 flex flex-wrap items-center justify-between gap-4 hover:bg-slate-50 transition-colors"> | |
| <div class="flex items-center gap-4"> | |
| <div id="check-${pillar.id}" class="w-6 h-6 rounded-full border-2 border-slate-200 flex items-center justify-center text-white transition-all"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> | |
| </div> | |
| <h2 class="text-lg font-bold text-slate-800">${pillar.title}</h2> | |
| </div> | |
| <div class="flex items-center gap-6"> | |
| <!-- Completion Info --> | |
| <div class="flex flex-col items-end"> | |
| <span class="text-[10px] uppercase font-bold text-slate-400">Progression</span> | |
| <div class="flex items-center gap-2"> | |
| <div class="w-24 h-1.5 bg-slate-100 rounded-full overflow-hidden"> | |
| <div id="progress-bar-${pillar.id}" class="h-full bg-slate-300 transition-all duration-500" style="width: 0%"></div> | |
| </div> | |
| <span id="progress-text-${pillar.id}" class="text-xs font-mono text-slate-500">0/${pillar.questions.length}</span> | |
| </div> | |
| </div> | |
| <!-- Score Info --> | |
| <div class="bg-indigo-50 text-indigo-700 px-3 py-1 rounded-lg text-sm font-bold border border-indigo-100"> | |
| <span id="score-val-${pillar.id}">0</span>% | |
| </div> | |
| <!-- Chevron --> | |
| <svg id="chevron-${pillar.id}" xmlns="http://www.w3.org/2000/svg" class="text-slate-400 transition-transform duration-300" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg> | |
| </div> | |
| </div> | |
| <!-- Accordion Content --> | |
| <div id="content-${pillar.id}" class="accordion-content border-t border-slate-100"> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full text-left"> | |
| <thead class="bg-slate-50/50"> | |
| <tr class="text-[10px] uppercase tracking-wider text-slate-400"> | |
| <th class="px-6 py-3 font-bold">Critères d'évaluation</th> | |
| <th class="px-2 py-3 text-center w-20">Starting</th> | |
| <th class="px-2 py-3 text-center w-20">Developing</th> | |
| <th class="px-2 py-3 text-center w-20">Maturing</th> | |
| <th class="px-2 py-3 text-center w-20">Performing</th> | |
| <th class="px-2 py-3 text-center w-20">Excelling</th> | |
| </tr> | |
| </thead> | |
| <tbody class="divide-y divide-slate-100"> | |
| ${pillar.questions.map((q, idx) => ` | |
| <tr> | |
| <td class="px-6 py-4 text-sm text-slate-700 font-medium">${q}</td> | |
| ${[0, 1, 2, 3, 4].map(level => ` | |
| <td class="px-1 py-4 text-center"> | |
| <button | |
| onclick="setScore('${pillar.id}', ${idx}, ${level})" | |
| id="btn-${pillar.id}-${idx}-${level}" | |
| class="level-btn level-${level} w-9 h-9 rounded-xl border-2 flex items-center justify-center text-[11px] font-bold mx-auto" | |
| > | |
| ${level + 1} | |
| </button> | |
| </td> | |
| `).join('')} | |
| </tr> | |
| `).join('')} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| function toggleAccordion(id) { | |
| const content = document.getElementById(`content-${id}`); | |
| const chevron = document.getElementById(`chevron-${id}`); | |
| const isOpen = content.classList.contains('open'); | |
| // Close others (optional - commented out to allow multiple open) | |
| // document.querySelectorAll('.accordion-content').forEach(c => c.classList.remove('open')); | |
| // document.querySelectorAll('[id^="chevron-"]').forEach(c => c.style.transform = 'rotate(0deg)'); | |
| if (isOpen) { | |
| content.classList.remove('open'); | |
| chevron.style.transform = 'rotate(0deg)'; | |
| } else { | |
| content.classList.add('open'); | |
| chevron.style.transform = 'rotate(180deg)'; | |
| } | |
| } | |
| function setScore(pillarId, qIdx, level) { | |
| scores[pillarId][qIdx] = level + 1; | |
| // Highlight button | |
| [0, 1, 2, 3, 4].forEach(l => { | |
| document.getElementById(`btn-${pillarId}-${qIdx}-${l}`).classList.remove(`active-${l}`); | |
| }); | |
| document.getElementById(`btn-${pillarId}-${qIdx}-${level}`).classList.add(`active-${level}`); | |
| updatePillarProgress(pillarId); | |
| updateCalculations(); | |
| } | |
| function updatePillarProgress(pillarId) { | |
| const pillarQuestions = scores[pillarId]; | |
| const answeredCount = pillarQuestions.filter(s => s > 0).length; | |
| const totalQuestions = pillarQuestions.length; | |
| const completionPercent = (answeredCount / totalQuestions) * 100; | |
| // Update UI Progress | |
| const bar = document.getElementById(`progress-bar-${pillarId}`); | |
| const text = document.getElementById(`progress-text-${pillarId}`); | |
| const check = document.getElementById(`check-${pillarId}`); | |
| bar.style.width = `${completionPercent}%`; | |
| text.innerText = `${answeredCount}/${totalQuestions}`; | |
| if (completionPercent === 100) { | |
| bar.classList.replace('bg-slate-300', 'bg-emerald-500'); | |
| check.classList.add('bg-emerald-500', 'border-emerald-500'); | |
| } else { | |
| bar.classList.add('bg-slate-300'); | |
| bar.classList.remove('bg-emerald-500'); | |
| check.classList.remove('bg-emerald-500', 'border-emerald-500'); | |
| } | |
| // Update Global Progress | |
| updateGlobalProgress(); | |
| } | |
| function updateGlobalProgress() { | |
| let totalQ = 0; | |
| let answeredQ = 0; | |
| dataStructure.forEach(p => { | |
| totalQ += p.questions.length; | |
| answeredQ += scores[p.id].filter(s => s > 0).length; | |
| }); | |
| const percent = (answeredQ / totalQ) * 100; | |
| document.getElementById('global-progress-bar').style.width = `${percent}%`; | |
| } | |
| function updateCalculations() { | |
| const pillarScores = dataStructure.map(p => { | |
| const answered = scores[p.id].filter(s => s > 0); | |
| if (answered.length === 0) return 0; | |
| // Score calculates based on average of answered questions | |
| const sum = answered.reduce((a, b) => a + b, 0); | |
| const percentage = (sum / (p.questions.length * 5)) * 100; | |
| document.getElementById(`score-val-${p.id}`).innerText = Math.round(percentage); | |
| return Math.round(percentage); | |
| }); | |
| radarChart.data.datasets[0].data = pillarScores; | |
| radarChart.update(); | |
| barChart.data.datasets[0].data = pillarScores; | |
| barChart.data.datasets[0].backgroundColor = pillarScores.map(val => { | |
| if (val < 40) return '#ef4444'; | |
| if (val < 70) return '#f59e0b'; | |
| return '#22c55e'; | |
| }); | |
| barChart.update(); | |
| } | |
| function initCharts() { | |
| const labels = dataStructure.map(p => p.title); | |
| const radarCtx = document.getElementById('radarChart').getContext('2d'); | |
| radarChart = new Chart(radarCtx, { | |
| type: 'radar', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| label: 'Maturité Actuelle', | |
| data: labels.map(() => 0), | |
| backgroundColor: 'rgba(99, 102, 241, 0.2)', | |
| borderColor: '#6366f1', | |
| pointBackgroundColor: '#6366f1', | |
| borderWidth: 2 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| r: { beginAtZero: true, max: 100, ticks: { display: false } } | |
| }, | |
| plugins: { legend: { display: false } } | |
| } | |
| }); | |
| const barCtx = document.getElementById('barChart').getContext('2d'); | |
| barChart = new Chart(barCtx, { | |
| type: 'bar', | |
| data: { | |
| labels: labels, | |
| datasets: [{ data: labels.map(() => 0), borderRadius: 6 }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { beginAtZero: true, max: 100, grid: { borderDash: [5, 5] } }, | |
| x: { grid: { display: false }, ticks: { font: { size: 9 } } } | |
| }, | |
| plugins: { legend: { display: false } } | |
| } | |
| }); | |
| } | |
| async function exportToPDF() { | |
| const toast = document.getElementById('toast'); | |
| toast.style.opacity = '1'; | |
| toast.style.transform = 'translateY(0)'; | |
| // Open all accordions for PDF | |
| document.querySelectorAll('.accordion-content').forEach(c => c.classList.add('open')); | |
| const element = document.body; | |
| const opt = { | |
| margin: [0.2, 0.2], | |
| filename: 'Rapport_Maturite_OpEx.pdf', | |
| html2canvas: { scale: 2 }, | |
| jsPDF: { unit: 'in', format: 'a4', orientation: 'landscape' } | |
| }; | |
| try { | |
| await html2pdf().set(opt).from(element).save(); | |
| } finally { | |
| setTimeout(() => { | |
| toast.style.opacity = '0'; | |
| toast.style.transform = 'translateY(20px)'; | |
| }, 3000); | |
| } | |
| } | |
| window.onload = initApp; | |
| </script> | |
| </body> | |
| </html> | |