{"id":800,"date":"2026-09-08T08:07:15","date_gmt":"2026-09-08T08:07:15","guid":{"rendered":"https:\/\/maroczain.com\/scolaire.maroczain.com\/?page_id=800"},"modified":"2026-09-08T08:07:15","modified_gmt":"2026-09-08T08:07:15","slug":"gestionnaire-des-roles","status":"publish","type":"page","link":"https:\/\/maroczain.com\/scolaire.maroczain.com\/gestionnaire-des-roles\/","title":{"rendered":"Gestionnaire des R\u00f4les"},"content":{"rendered":"\n<!-- MZA ACADEMY OS \u2014 WORDPRESS SAFE -->\n<div id=\"mza-os-safe-v3\"><\/div>\n\n<style>\n#mza-os-safe-v3{--bg:#07111f;--panel:#0d1b2d;--panel2:#12243b;--soft:#17314f;--text:#f5f7fb;--muted:#9fb0c6;--line:#234260;--accent:#d8b15b;--ok:#26b36b;--warn:#f0a43a;--danger:#e65b66;--info:#4ea1ff;font-family:Arial,Helvetica,sans-serif;background:var(--bg);color:var(--text);min-height:100vh}\n#mza-os-safe-v3 *{box-sizing:border-box}\n#mza-os-safe-v3 button,#mza-os-safe-v3 input,#mza-os-safe-v3 select{font:inherit}\n#mza-os-safe-v3 button{cursor:pointer}\n#mza-os-safe-v3 .app{display:grid;grid-template-columns:270px 1fr;min-height:100vh}\n#mza-os-safe-v3 .sidebar{background:#081522;border-right:1px solid var(--line);padding:18px;min-height:100vh}\n#mza-os-safe-v3 .brand{padding:8px 8px 18px;border-bottom:1px solid var(--line);margin-bottom:14px}\n#mza-os-safe-v3 .brand h1{font-size:20px;margin:0;color:var(--accent)}\n#mza-os-safe-v3 .brand p{font-size:12px;margin:4px 0 0;color:var(--muted)}\n#mza-os-safe-v3 .nav{display:grid;gap:6px}\n#mza-os-safe-v3 .nav button{width:100%;text-align:left;background:transparent;color:var(--text);border:1px solid transparent;padding:11px 12px;border-radius:10px}\n#mza-os-safe-v3 .nav button:hover,#mza-os-safe-v3 .nav button.active{background:var(--panel2);border-color:var(--line)}\n#mza-os-safe-v3 .sec{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin:12px 8px 4px}\n#mza-os-safe-v3 .main{min-width:0}\n#mza-os-safe-v3 .topbar{padding:14px 22px;display:flex;gap:10px;align-items:center;border-bottom:1px solid var(--line);background:#07111f;position:sticky;top:0;z-index:5}\n#mza-os-safe-v3 .search{flex:1}\n#mza-os-safe-v3 .search input{width:100%}\n#mza-os-safe-v3 input,#mza-os-safe-v3 select{background:#091522;border:1px solid var(--line);color:var(--text);padding:10px;border-radius:9px}\n#mza-os-safe-v3 .btn{border:1px solid var(--line);background:var(--panel2);color:var(--text);padding:10px 12px;border-radius:10px}\n#mza-os-safe-v3 .btn.primary{background:var(--accent);color:#221b0b;font-weight:800;border-color:var(--accent)}\n#mza-os-safe-v3 .btn.danger{background:#4a1720;border-color:#7d2635}\n#mza-os-safe-v3 .content{padding:22px}\n#mza-os-safe-v3 .view{display:none}\n#mza-os-safe-v3 .view.active{display:block}\n#mza-os-safe-v3 .head{display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:18px}\n#mza-os-safe-v3 .head h2{margin:0 0 5px}\n#mza-os-safe-v3 .head p{margin:0;color:var(--muted)}\n#mza-os-safe-v3 .grid{display:grid;gap:14px}\n#mza-os-safe-v3 .kpis{grid-template-columns:repeat(4,minmax(0,1fr))}\n#mza-os-safe-v3 .two{grid-template-columns:1.35fr .95fr}\n#mza-os-safe-v3 .card{background:linear-gradient(180deg,var(--panel),#0b1828);border:1px solid var(--line);border-radius:16px;padding:16px}\n#mza-os-safe-v3 .label{font-size:12px;color:var(--muted)}\n#mza-os-safe-v3 .value{font-size:25px;font-weight:800;margin-top:6px}\n#mza-os-safe-v3 .small{font-size:12px;color:var(--muted)}\n#mza-os-safe-v3 table{width:100%;border-collapse:collapse}\n#mza-os-safe-v3 th,#mza-os-safe-v3 td{text-align:left;padding:10px;border-bottom:1px solid var(--line);font-size:13px;vertical-align:top}\n#mza-os-safe-v3 th{font-size:12px;color:#cdd8e5}\n#mza-os-safe-v3 .badge{display:inline-block;padding:5px 8px;border-radius:999px;font-size:11px;border:1px solid var(--line)}\n#mza-os-safe-v3 .ok{background:#103925;color:#79e7aa}\n#mza-os-safe-v3 .warn{background:#493414;color:#ffd18a}\n#mza-os-safe-v3 .danger{background:#481923;color:#ff9aa5}\n#mza-os-safe-v3 .info{background:#122f50;color:#9bc7ff}\n#mza-os-safe-v3 .alert{padding:12px;border-radius:12px;border:1px solid var(--line);background:var(--panel2);margin-bottom:9px}\n#mza-os-safe-v3 .alert strong{display:block;margin-bottom:4px}\n#mza-os-safe-v3 .modal{position:fixed;inset:0;background:rgba(0,0,0,.7);display:none;align-items:center;justify-content:center;z-index:99999;padding:20px}\n#mza-os-safe-v3 .modal.open{display:flex}\n#mza-os-safe-v3 .modalbox{width:min(760px,96vw);max-height:90vh;overflow:auto;background:#0b1828;border:1px solid var(--line);border-radius:18px;padding:20px}\n#mza-os-safe-v3 .formgrid{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n#mza-os-safe-v3 label{display:grid;gap:6px;font-size:12px;color:var(--muted)}\n#mza-os-safe-v3 .actions{display:flex;justify-content:flex-end;gap:8px;margin-top:14px}\n#mza-os-safe-v3 .hidden{display:none!important}\n@media(max-width:1000px){#mza-os-safe-v3 .app{grid-template-columns:1fr}#mza-os-safe-v3 .sidebar{min-height:auto}#mza-os-safe-v3 .nav{grid-template-columns:repeat(2,1fr)}#mza-os-safe-v3 .kpis,#mza-os-safe-v3 .two{grid-template-columns:1fr 1fr}}\n@media(max-width:650px){#mza-os-safe-v3 .topbar{flex-wrap:wrap}#mza-os-safe-v3 .search{flex-basis:100%}#mza-os-safe-v3 .kpis,#mza-os-safe-v3 .two,#mza-os-safe-v3 .formgrid{grid-template-columns:1fr}}\n<\/style>\n\n<script>\n(function(){\n  const ROOT_ID='mza-os-safe-v3';\n  const root=document.getElementById(ROOT_ID);\n  if(!root || root.dataset.initialized==='1') return;\n  root.dataset.initialized='1';\n\n  const STORE='mza_os_safe_v3_db';\n  const seed={\n    companies:[\n      {id:1,name:'Royal Air Maroc',contact:'Direction Formation',status:'Prospect strat\u00e9gique',revenue:180000},\n      {id:2,name:'Atlas Handling',contact:'RH',status:'Client actif',revenue:92000}\n    ],\n    trainings:[\n      {id:1,title:'Amadeus Training',capacity:20,enrolled:15,price:4800,cost:42000,status:'Ouverte'},\n      {id:2,title:'Agent d\u2019escale',capacity:18,enrolled:18,price:5500,cost:51000,status:'Compl\u00e8te'},\n      {id:3,title:'Repr\u00e9sentant r\u00e9gional',capacity:16,enrolled:9,price:6900,cost:47000,status:'Ouverte'}\n    ],\n    invoices:[\n      {id:1,ref:'FAC-2026-052',client:'Royal Air Maroc',amount:78000,paid:25000,due:'2026-09-15',status:'Partielle'},\n      {id:2,ref:'FAC-2026-056',client:'MZA Voyages',amount:18000,paid:0,due:'2026-09-20',status:'\u00c0 encaisser'}\n    ],\n    contracts:[\n      {id:1,name:'Prestataire salles',supplier:'Centre El Jadida',value:36000,expiry:'2026-10-15',notice:30,owner:'Direction',score:88,docs:'Complet',status:'Actif'},\n      {id:2,name:'Maintenance informatique',supplier:'Tech Services',value:24000,expiry:'2026-09-25',notice:30,owner:'DOSI',score:72,docs:'Attestation fiscale manquante',status:'\u00c0 renouveler'},\n      {id:3,name:'Impression certificats',supplier:'Print Pro',value:12000,expiry:'2027-01-10',notice:45,owner:'P\u00e9dagogie',score:91,docs:'Complet',status:'Actif'}\n    ],\n    roles:[\n      {id:1,name:'PDG \/ Super Admin',modules:['dashboard','crm','training','finance','contracts','rbac','admin'],actions:['view','create','edit','delete','export','admin']},\n      {id:2,name:'Direction Acad\u00e9mie',modules:['dashboard','crm','training','contracts'],actions:['view','create','edit','export']},\n      {id:3,name:'Finance',modules:['dashboard','finance','contracts'],actions:['view','create','edit','export']},\n      {id:4,name:'Commercial',modules:['crm','training'],actions:['view','create','edit']},\n      {id:5,name:'Formateur',modules:['training'],actions:['view']}\n    ],\n    users:[\n      {id:1,name:'Mohammed KIAL',roleId:1,status:'Actif'},\n      {id:2,name:'Responsable Finance',roleId:3,status:'Actif'},\n      {id:3,name:'Responsable Commercial',roleId:4,status:'Actif'},\n      {id:4,name:'Formateur D\u00e9mo',roleId:5,status:'Actif'}\n    ],\n    audit:[],\n    currentUserId:1\n  };\n  let db;\n  try{db=JSON.parse(localStorage.getItem(STORE))||JSON.parse(JSON.stringify(seed));}\n  catch(e){db=JSON.parse(JSON.stringify(seed));}\n\n  const money=n=>new Intl.NumberFormat('fr-MA',{style:'currency',currency:'MAD',maximumFractionDigits:0}).format(Number(n||0));\n  const daysUntil=s=>Math.ceil((new Date(s+'T12:00:00')-new Date())\/86400000);\n  const currentUser=()=>db.users.find(x=>x.id===Number(db.currentUserId))||db.users[0];\n  const currentRole=()=>db.roles.find(x=>x.id===currentUser().roleId)||db.roles[0];\n  const canModule=m=>currentRole().modules.includes(m);\n  const can=a=>currentRole().actions.includes(a);\n  const save=()=>{localStorage.setItem(STORE,JSON.stringify(db));render();};\n  const nextId=a=>a.length?Math.max(...a.map(x=>x.id||0))+1:1;\n  const audit=(action,target)=>{db.audit.unshift({id:nextId(db.audit),date:new Date().toISOString(),user:currentUser().name,action,target});db.audit=db.audit.slice(0,100);localStorage.setItem(STORE,JSON.stringify(db));};\n  const badge=s=>{let t=String(s).toLowerCase(),c='info';if(t.includes('actif')||t.includes('pay')||t.includes('complet'))c='ok';if(t.includes('part')||t.includes('renou')||t.includes('ouverte'))c='warn';if(t.includes('compl\u00e8te')||t.includes('expir'))c='danger';return `<span class=\"badge ${c}\">${s}<\/span>`};\n  const contractStatus=c=>{let d=daysUntil(c.expiry);if(d<0)return'Expir\u00e9';if(d<=7)return'Critique';if(d<=15)return'Urgent';if(d<=30)return'\u00c0 renouveler';if(d<=60)return'Pr\u00e9parer renouvellement';if(d<=90)return'\u00c0 surveiller';return'Actif';};\n  const contractAction=c=>{let d=daysUntil(c.expiry);if(d<0)return'Cl\u00f4turer \/ renouveler';if(d<=7)return'D\u00e9cision Direction imm\u00e9diate';if(d<=15)return'N\u00e9gociation finale';if(d<=Number(c.notice||30))return'Envoyer pr\u00e9avis \/ renouvellement';if(d<=60)return'Pr\u00e9parer comparatif fournisseur';if(d<=90)return'Contr\u00f4ler performance et documents';return'Surveillance automatique';};\n\n  root.innerHTML=`\n  <div class=\"app\">\n    <aside class=\"sidebar\">\n      <div class=\"brand\"><h1>MZA ACADEMY OS<\/h1><p>WordPress SAFE V3<\/p><\/div>\n      <div class=\"nav\">\n        <div class=\"sec\">Pilotage<\/div>\n        <button data-view=\"dashboard\">\ud83c\udfdb\ufe0f Cockpit PDG<\/button>\n        <button data-view=\"crm\">\ud83c\udfe2 CRM Entreprises<\/button>\n        <button data-view=\"training\">\ud83c\udf93 Formations & Sessions<\/button>\n        <button data-view=\"finance\">\ud83d\udcb0 Finance<\/button>\n        <button data-view=\"contracts\">\ud83d\udcd1 Contrats & Fournisseurs<\/button>\n        <button data-view=\"rbac\">\ud83d\udd10 R\u00f4les & Permissions<\/button>\n        <button data-view=\"admin\">\u2699\ufe0f Administration<\/button>\n      <\/div>\n    <\/aside>\n    <main class=\"main\">\n      <div class=\"topbar\">\n        <div class=\"search\"><input id=\"mza-search\" placeholder=\"Recherche globale...\"><\/div>\n        <select id=\"mza-user\"><\/select>\n        <button class=\"btn\" data-action=\"search\">Rechercher<\/button>\n        <button class=\"btn\" data-action=\"export\">Exporter<\/button>\n      <\/div>\n      <div class=\"content\">\n        <section class=\"view\" data-screen=\"dashboard\"><\/section>\n        <section class=\"view\" data-screen=\"crm\"><\/section>\n        <section class=\"view\" data-screen=\"training\"><\/section>\n        <section class=\"view\" data-screen=\"finance\"><\/section>\n        <section class=\"view\" data-screen=\"contracts\"><\/section>\n        <section class=\"view\" data-screen=\"rbac\"><\/section>\n        <section class=\"view\" data-screen=\"admin\"><\/section>\n      <\/div>\n    <\/main>\n  <\/div>\n  <div class=\"modal\" id=\"mza-modal\"><div class=\"modalbox\"><div id=\"mza-modal-content\"><\/div><\/div><\/div>`;\n\n  function metrics(){\n    const ca=db.invoices.reduce((s,x)=>s+x.amount,0), paid=db.invoices.reduce((s,x)=>s+x.paid,0);\n    const en=db.trainings.reduce((s,x)=>s+x.enrolled,0), cap=db.trainings.reduce((s,x)=>s+x.capacity,0);\n    const rev=db.trainings.reduce((s,x)=>s+x.enrolled*x.price,0), cost=db.trainings.reduce((s,x)=>s+x.cost,0);\n    return {ca,paid,due:ca-paid,fill:cap?Math.round(en\/cap*100):0,en,margin:rev-cost};\n  }\n  function renderDashboard(){\n    const m=metrics(), contracts=[...db.contracts].sort((a,b)=>daysUntil(a.expiry)-daysUntil(b.expiry));\n    root.querySelector('[data-screen=\"dashboard\"]').innerHTML=`\n      <div class=\"head\"><div><h2>Cockpit PDG<\/h2><p>Vue consolid\u00e9e de Maroc Zain Academy.<\/p><\/div><div class=\"small\">${new Date().toLocaleDateString('fr-FR',{weekday:'long',year:'numeric',month:'long',day:'numeric'})}<\/div><\/div>\n      <div class=\"grid kpis\">\n        <div class=\"card\"><div class=\"label\">CA factur\u00e9<\/div><div class=\"value\">${money(m.ca)}<\/div><\/div>\n        <div class=\"card\"><div class=\"label\">Encaissements<\/div><div class=\"value\">${money(m.paid)}<\/div><div class=\"small\">${money(m.due)} \u00e0 recevoir<\/div><\/div>\n        <div class=\"card\"><div class=\"label\">Taux remplissage<\/div><div class=\"value\">${m.fill}%<\/div><div class=\"small\">${m.en} inscriptions<\/div><\/div>\n        <div class=\"card\"><div class=\"label\">Marge formations<\/div><div class=\"value\">${money(m.margin)}<\/div><\/div>\n      <\/div>\n      <div class=\"grid two\" style=\"margin-top:14px\">\n        <div class=\"card\"><h3>MZA Decision Engine<\/h3>\n          <div class=\"alert\"><strong>Encaissement<\/strong><div class=\"small\">${money(m.due)} restent \u00e0 recevoir.<\/div><\/div>\n          ${db.trainings.filter(x=>x.enrolled\/x.capacity<.65).map(x=>`<div class=\"alert\"><strong>${x.title}<\/strong><div class=\"small\">Remplissage faible : action commerciale recommand\u00e9e.<\/div><\/div>`).join('')}\n        <\/div>\n        <div class=\"card\"><h3>Contrats prioritaires<\/h3>\n          ${contracts.slice(0,3).map(c=>`<div class=\"alert\"><strong>${c.name}<\/strong><div class=\"small\">${c.supplier} \u00b7 ${daysUntil(c.expiry)>=0?'J-'+daysUntil(c.expiry):'Expir\u00e9'} \u00b7 ${contractAction(c)}<\/div><\/div>`).join('')}\n        <\/div>\n      <\/div>`;\n  }\n  function table(headers,rows){return `<div style=\"overflow:auto\"><table><thead><tr>${headers.map(h=>`<th>${h}<\/th>`).join('')}<th>Actions<\/th><\/tr><\/thead><tbody>${rows.join('')}<\/tbody><\/table><\/div>`}\n  function act(type,id){let s='';if(can('edit'))s+=`<button class=\"btn\" data-action=\"edit\" data-type=\"${type}\" data-id=\"${id}\">Modifier<\/button> `;if(can('delete'))s+=`<button class=\"btn danger\" data-action=\"delete\" data-type=\"${type}\" data-id=\"${id}\">Supprimer<\/button>`;return s||'<span class=\"small\">Lecture seule<\/span>'}\n  function renderCRUD(){\n    root.querySelector('[data-screen=\"crm\"]').innerHTML=`<div class=\"head\"><div><h2>CRM Entreprises<\/h2><p>Prospects et clients.<\/p><\/div>${can('create')?'<button class=\"btn primary\" data-action=\"add\" data-type=\"companies\">+ Entreprise<\/button>':''}<\/div><div class=\"card\">${table(['Entreprise','Contact','Statut','CA'],db.companies.map(x=>`<tr><td><strong>${x.name}<\/strong><\/td><td>${x.contact}<\/td><td>${badge(x.status)}<\/td><td>${money(x.revenue)}<\/td><td>${act('companies',x.id)}<\/td><\/tr>`))}<\/div>`;\n    root.querySelector('[data-screen=\"training\"]').innerHTML=`<div class=\"head\"><div><h2>Formations & Sessions<\/h2><p>Pilotage p\u00e9dagogique.<\/p><\/div>${can('create')?'<button class=\"btn primary\" data-action=\"add\" data-type=\"trainings\">+ Session<\/button>':''}<\/div><div class=\"card\">${table(['Formation','Remplissage','Prix','Co\u00fbt','Statut'],db.trainings.map(x=>`<tr><td><strong>${x.title}<\/strong><\/td><td>${x.enrolled}\/${x.capacity}<\/td><td>${money(x.price)}<\/td><td>${money(x.cost)}<\/td><td>${badge(x.status)}<\/td><td>${act('trainings',x.id)}<\/td><\/tr>`))}<\/div>`;\n    root.querySelector('[data-screen=\"finance\"]').innerHTML=`<div class=\"head\"><div><h2>Finance<\/h2><p>Factures et encaissements.<\/p><\/div>${can('create')?'<button class=\"btn primary\" data-action=\"add\" data-type=\"invoices\">+ Facture<\/button>':''}<\/div><div class=\"card\">${table(['R\u00e9f.','Client','Montant','Pay\u00e9','Reste','\u00c9ch\u00e9ance'],db.invoices.map(x=>`<tr><td>${x.ref}<\/td><td>${x.client}<\/td><td>${money(x.amount)}<\/td><td>${money(x.paid)}<\/td><td>${money(x.amount-x.paid)}<\/td><td>${x.due}<\/td><td>${act('invoices',x.id)}<\/td><\/tr>`))}<\/div>`;\n    root.querySelector('[data-screen=\"contracts\"]').innerHTML=`<div class=\"head\"><div><h2>Contrats & Fournisseurs \u2014 AutoPilot<\/h2><p>\u00c9ch\u00e9ances et renouvellements automatis\u00e9s.<\/p><\/div>${can('create')?'<button class=\"btn primary\" data-action=\"add\" data-type=\"contracts\">+ Contrat<\/button>':''}<\/div><div class=\"card\">${table(['Contrat','Fournisseur','Valeur','\u00c9ch\u00e9ance','Statut auto','Action'],db.contracts.map(x=>`<tr><td><strong>${x.name}<\/strong><div class=\"small\">${x.owner}<\/div><\/td><td>${x.supplier}<\/td><td>${money(x.value)}<\/td><td>${x.expiry}<div class=\"small\">${daysUntil(x.expiry)>=0?'J-'+daysUntil(x.expiry):'Expir\u00e9'}<\/div><\/td><td>${badge(contractStatus(x))}<\/td><td>${contractAction(x)}<\/td><td>${act('contracts',x.id)}<\/td><\/tr>`))}<\/div>`;\n  }\n  function renderRBAC(){\n    root.querySelector('[data-screen=\"rbac\"]').innerHTML=`\n      <div class=\"head\"><div><h2>R\u00f4les & Permissions<\/h2><p>Qui peut voir quoi.<\/p><\/div><\/div>\n      <div class=\"grid two\">\n        <div class=\"card\"><h3>Utilisateurs<\/h3>${table(['Utilisateur','R\u00f4le','Statut'],db.users.map(u=>{let r=db.roles.find(x=>x.id===u.roleId);return `<tr><td>${u.name}<\/td><td>${r?r.name:'\u2014'}<\/td><td>${badge(u.status)}<\/td><td><\/td><\/tr>`}))}<\/div>\n        <div class=\"card\"><h3>R\u00f4les<\/h3>${db.roles.map(r=>`<div class=\"alert\"><strong>${r.name}<\/strong><div class=\"small\">${r.modules.join(', ')}<\/div><div class=\"small\">Actions : ${r.actions.join(', ')}<\/div><\/div>`).join('')}<\/div>\n      <\/div>\n      <div class=\"card\" style=\"margin-top:14px\"><h3>Journal d\u2019audit<\/h3>${table(['Date','Utilisateur','Action','Cible'],db.audit.slice(0,20).map(a=>`<tr><td>${new Date(a.date).toLocaleString('fr-FR')}<\/td><td>${a.user}<\/td><td>${a.action}<\/td><td>${a.target}<\/td><td><\/td><\/tr>`))}<\/div>`;\n  }\n  function renderAdmin(){\n    root.querySelector('[data-screen=\"admin\"]').innerHTML=`<div class=\"head\"><div><h2>Administration<\/h2><p>Maintenance locale.<\/p><\/div><\/div><div class=\"grid two\"><div class=\"card\"><h3>Exporter<\/h3><button class=\"btn\" data-action=\"export\">T\u00e9l\u00e9charger JSON<\/button><\/div><div class=\"card\"><h3>R\u00e9initialiser<\/h3><button class=\"btn danger\" data-action=\"reset\">R\u00e9initialiser les donn\u00e9es<\/button><\/div><\/div>`;\n  }\n  function renderUser(){\n    const s=root.querySelector('#mza-user');\n    s.innerHTML=db.users.filter(x=>x.status==='Actif').map(u=>`<option value=\"${u.id}\" ${u.id===Number(db.currentUserId)?'selected':''}>${u.name}<\/option>`).join('');\n  }\n  function applyPermissions(){\n    root.querySelectorAll('[data-view]').forEach(b=>b.classList.toggle('hidden',!canModule(b.dataset.view)));\n  }\n  function show(view){\n    if(!canModule(view)){alert('Acc\u00e8s refus\u00e9 pour ce r\u00f4le.');audit('Acc\u00e8s refus\u00e9',view);return;}\n    root.querySelectorAll('.view').forEach(v=>v.classList.remove('active'));\n    const sc=root.querySelector(`[data-screen=\"${view}\"]`); if(sc)sc.classList.add('active');\n    root.querySelectorAll('[data-view]').forEach(b=>b.classList.toggle('active',b.dataset.view===view));\n  }\n  function render(){\n    renderDashboard();renderCRUD();renderRBAC();renderAdmin();renderUser();applyPermissions();\n    let active=root.querySelector('.view.active');\n    if(!active || !canModule(active.dataset.screen)){\n      let first=[...root.querySelectorAll('[data-view]')].find(b=>!b.classList.contains('hidden'));\n      if(first)show(first.dataset.view);\n    }\n  }\n\n  const schemas={\n    companies:[['name','Entreprise','text'],['contact','Contact','text'],['status','Statut','text'],['revenue','CA','number']],\n    trainings:[['title','Formation','text'],['capacity','Capacit\u00e9','number'],['enrolled','Inscrits','number'],['price','Prix','number'],['cost','Co\u00fbt','number'],['status','Statut','text']],\n    invoices:[['ref','R\u00e9f\u00e9rence','text'],['client','Client','text'],['amount','Montant','number'],['paid','Pay\u00e9','number'],['due','\u00c9ch\u00e9ance','date'],['status','Statut','text']],\n    contracts:[['name','Contrat','text'],['supplier','Fournisseur','text'],['value','Valeur','number'],['expiry','\u00c9ch\u00e9ance','date'],['notice','Pr\u00e9avis jours','number'],['owner','Responsable','text'],['score','Score \/100','number'],['docs','Documents','text'],['status','Statut','text']]\n  };\n  function openForm(type,id){\n    if(id && !can('edit'))return alert('Modification non autoris\u00e9e.');\n    if(!id && !can('create'))return alert('Cr\u00e9ation non autoris\u00e9e.');\n    const item=id?db[type].find(x=>x.id===id):{};\n    const fields=schemas[type];\n    root.querySelector('#mza-modal-content').innerHTML=`\n      <div class=\"head\"><div><h2>${id?'Modifier':'Nouvelle fiche'}<\/h2><p>${type}<\/p><\/div><button class=\"btn\" data-action=\"close\">\u2715<\/button><\/div>\n      <form id=\"mza-form\" data-type=\"${type}\" data-id=\"${id||''}\">\n        <div class=\"formgrid\">${fields.map(([k,l,t])=>`<label>${l}<input name=\"${k}\" type=\"${t}\" value=\"${item[k]??''}\" required><\/label>`).join('')}<\/div>\n        <div class=\"actions\"><button type=\"button\" class=\"btn\" data-action=\"close\">Annuler<\/button><button class=\"btn primary\" type=\"submit\">Enregistrer<\/button><\/div>\n      <\/form>`;\n    root.querySelector('#mza-modal').classList.add('open');\n  }\n  function closeModal(){root.querySelector('#mza-modal').classList.remove('open')}\n  function doSearch(){\n    const q=root.querySelector('#mza-search').value.trim().toLowerCase(); if(!q)return;\n    let results=[];\n    ['companies','trainings','invoices','contracts'].forEach(t=>db[t].forEach(x=>{if(JSON.stringify(x).toLowerCase().includes(q))results.push({t,x})}));\n    root.querySelector('#mza-modal-content').innerHTML=`<div class=\"head\"><div><h2>R\u00e9sultats<\/h2><p>${results.length} r\u00e9sultat(s)<\/p><\/div><button class=\"btn\" data-action=\"close\">\u2715<\/button><\/div>${results.map(r=>`<div class=\"alert\"><strong>${r.t}<\/strong><div class=\"small\">${Object.values(r.x).join(' \u2014 ')}<\/div><\/div>`).join('')||'<div class=\"alert\">Aucun r\u00e9sultat.<\/div>'}`;\n    root.querySelector('#mza-modal').classList.add('open');\n  }\n  function doExport(){\n    if(!can('export'))return alert('Export non autoris\u00e9.');\n    audit('Export','Base MZA');\n    const blob=new Blob([JSON.stringify(db,null,2)],{type:'application\/json'});\n    const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='mza-academy-os-backup.json';document.body.appendChild(a);a.click();a.remove();\n  }\n\n  root.addEventListener('click',function(e){\n    const btn=e.target.closest('button'); if(!btn)return;\n    if(btn.dataset.view){show(btn.dataset.view);return;}\n    const a=btn.dataset.action;\n    if(a==='add')openForm(btn.dataset.type);\n    if(a==='edit')openForm(btn.dataset.type,Number(btn.dataset.id));\n    if(a==='delete'){\n      if(!can('delete'))return alert('Suppression non autoris\u00e9e.');\n      if(confirm('Supprimer cette fiche ?')){db[btn.dataset.type]=db[btn.dataset.type].filter(x=>x.id!==Number(btn.dataset.id));audit('Suppression',btn.dataset.type+' #'+btn.dataset.id);save();}\n    }\n    if(a==='close')closeModal();\n    if(a==='search')doSearch();\n    if(a==='export')doExport();\n    if(a==='reset' && confirm('R\u00e9initialiser toutes les donn\u00e9es ?')){db=JSON.parse(JSON.stringify(seed));save();}\n  });\n\n  root.addEventListener('submit',function(e){\n    if(e.target.id!=='mza-form')return;\n    e.preventDefault();\n    const type=e.target.dataset.type,id=Number(e.target.dataset.id||0),fd=new FormData(e.target),data={};\n    schemas[type].forEach(([k,l,t])=>data[k]=t==='number'?Number(fd.get(k)):fd.get(k));\n    if(id){Object.assign(db[type].find(x=>x.id===id),data);audit('Modification',type+' #'+id);}\n    else{data.id=nextId(db[type]);db[type].push(data);audit('Cr\u00e9ation',type+' #'+data.id);}\n    closeModal();save();\n  });\n\n  root.querySelector('#mza-user').addEventListener('change',function(){\n    db.currentUserId=Number(this.value);audit('Changement utilisateur',currentUser().name);save();\n  });\n\n  root.querySelector('#mza-search').addEventListener('keydown',e=>{if(e.key==='Enter')doSearch();});\n  render();\n})();\n<\/script>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-800","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/pages\/800","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/comments?post=800"}],"version-history":[{"count":5,"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/pages\/800\/revisions"}],"predecessor-version":[{"id":806,"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/pages\/800\/revisions\/806"}],"wp:attachment":[{"href":"https:\/\/maroczain.com\/scolaire.maroczain.com\/wp-json\/wp\/v2\/media?parent=800"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}