*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} :root{ –or:#E8690A;–or2:#FF7B1F;–gold:#F5A41F;–bleu:#1D3557; –bg:#000;–bg2:#050505;–line:rgba(255,255,255,.07);–lineOr:rgba(232,105,10,.25); –t:#F5F5F7;–t2:rgba(245,245,247,.65);–t3:rgba(245,245,247,.3);–t4:rgba(245,245,247,.1); –green:#34D399;–red:#FF3B30;–blue:#60A5FA;–purple:#A78BFA; } *{box-sizing:border-box} body{background:var(–bg);color:var(–t);font-family:’Inter’,-apple-system,sans-serif;height:100vh;width:100vw;overflow:hidden;display:flex} ::-webkit-scrollbar{width:3px;height:3px} ::-webkit-scrollbar-thumb{background:rgba(232,105,10,.25);border-radius:2px} .btn{cursor:pointer;border:none;font-family:’Inter’,sans-serif;transition:all .2s;background:transparent} .btn:hover{opacity:.85;transform:translateY(-1px)}.btn:active{transform:translateY(0)} @keyframes fadeUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}} .fade{animation:fadeUp .35s cubic-bezier(.16,1,.3,1) both} .sonar{width:7px;height:7px;border-radius:50%;background:var(–or);display:inline-block;box-shadow:0 0 0 0 rgba(232,105,10,.5);animation:sonar 2s infinite} @keyframes sonar{0%{box-shadow:0 0 0 0 rgba(232,105,10,.6)}70%{box-shadow:0 0 0 9px rgba(232,105,10,0)}100%{box-shadow:0 0 0 0 rgba(232,105,10,0)}} .card{background:rgba(255,255,255,.025);border:1px solid var(–line);border-radius:16px;transition:border-color .25s,background .25s} .bdg{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;font-size:10px;font-weight:700;letter-spacing:.8px;text-transform:uppercase} .inp{width:100%;padding:11px 14px;background:rgba(255,255,255,.04);border:1px solid var(–line);border-radius:10px;color:var(–t);font-size:13px;outline:none;font-family:’Inter’,sans-serif;transition:border-color .2s} .inp:focus{border-color:var(–or)} select.inp option{background:#111;color:var(–t)} textarea.inp{resize:vertical} .toast{position:fixed;bottom:24px;right:24px;z-index:9999;padding:13px 22px;border-radius:12px;font-size:13px;font-weight:600;animation:fadeUp .3s cubic-bezier(.16,1,.3,1)} #sidebar{width:250px;background:#050505;border-right:1px solid var(–line);display:flex;flex-direction:column;padding:20px 12px;flex-shrink:0} .brand{padding:4px 6px 18px;border-bottom:1px solid var(–line);margin-bottom:16px} .nav-btn{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:10px;color:var(–t3);font-size:13px;font-weight:400;text-align:left;width:100%;border:1px solid transparent;transition:all .2s;margin-bottom:3px} .nav-btn.active{background:rgba(232,105,10,.1);color:var(–or);border-color:var(–lineOr);font-weight:700} .nav-btn:hover:not(.active){background:rgba(255,255,255,.04);color:var(–t2)} .credits-widget{background:linear-gradient(145deg,rgba(232,105,10,.1),rgba(232,105,10,.04));border:1px solid var(–lineOr);border-radius:12px;padding:14px 16px;margin-bottom:14px} #main{flex:1;overflow:auto;padding:32px 36px} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px} .grid-2{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:14px} .pill{padding:7px 16px;border-radius:980px;font-size:12px;font-weight:700;background:rgba(255,255,255,.03);color:var(–t3);border:1px solid var(–line);cursor:pointer;transition:all .2s} .pill.active{background:rgba(232,105,10,.12);color:var(–or);border-color:var(–lineOr)} .modal-bg{position:fixed;inset:0;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;z-index:1000;backdrop-filter:blur(8px)} .modal{background:#070707;border:1px solid var(–lineOr);border-radius:20px;padding:30px 34px;box-shadow:0 40px 100px rgba(0,0,0,.8)} #login-screen{position:fixed;inset:0;background:var(–bg);display:flex;align-items:center;justify-content:center;z-index:200} .login-mesh{position:absolute;inset:0;background:radial-gradient(ellipse 100% 80% at 50% 0%,rgba(232,105,10,.18) 0%,transparent 60%),radial-gradient(ellipse 70% 60% at 0% 100%,rgba(29,53,87,.45) 0%,transparent 55%);pointer-events:none} .login-card{background:rgba(255,255,255,.03);border:1px solid var(–line);border-radius:20px;padding:36px} .lbl{display:block;font-size:10px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(–t3);margin-bottom:8px} .glow-btn{background:linear-gradient(135deg,var(–or),var(–or2));border-radius:980px;color:#fff;font-size:14px;font-weight:800;width:100%;padding:14px;box-shadow:0 0 40px rgba(232,105,10,.45);transition:all .3s;border:none;cursor:pointer;font-family:’Inter’,sans-serif} .glow-btn:hover{box-shadow:0 0 60px rgba(232,105,10,.6);transform:scale(1.02)} .glow-sm{background:linear-gradient(135deg,var(–or),var(–or2));border-radius:980px;color:#fff;font-size:13px;font-weight:800;padding:10px 20px;box-shadow:0 0 30px rgba(232,105,10,.35);display:inline-flex;align-items:center;gap:8px;border:none;cursor:pointer;font-family:’Inter’,sans-serif;transition:all .2s} .glow-sm:hover{box-shadow:0 0 50px rgba(232,105,10,.55);opacity:.9} .eyebrow{font-size:11px;font-weight:700;letter-spacing:2.5px;text-transform:uppercase;color:var(–or);display:flex;align-items:center;gap:8px;margin-bottom:10px} .page-title{font-size:28px;font-weight:900;letter-spacing:-1.5px;color:var(–t);line-height:1;margin-bottom:4px} .log-item{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid var(–line)} .log-item:last-child{border-bottom:none} .stat-box{background:rgba(255,255,255,.03);border:1px solid var(–line);border-radius:12px;padding:14px 18px;text-align:center} .danger-btn{background:rgba(255,59,48,.1);border:1px solid rgba(255,59,48,.25);color:var(–red);border-radius:8px;padding:7px 14px;font-size:12px;font-weight:700;cursor:pointer;font-family:’Inter’,sans-serif;transition:all .2s} .danger-btn:hover{background:rgba(255,59,48,.2)} .note-bubble{background:rgba(255,255,255,.04);border:1px solid var(–line);border-radius:10px;padding:10px 14px;font-size:12px;color:var(–t2);line-height:1.55;margin-bottom:6px;position:relative} .note-bubble .note-date{font-size:10px;color:var(–t3);margin-top:6px} /* ── KANBAN ── */ .kanban-wrap{display:flex;gap:10px;overflow-x:auto;padding-bottom:12px;min-height:60vh} .kanban-wrap::-webkit-scrollbar{height:4px} .k-col{flex-shrink:0;width:230px;display:flex;flex-direction:column;border-radius:14px;overflow:hidden} .k-col-head{padding:12px 14px;font-size:11px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;display:flex;align-items:center;justify-content:space-between;flex-shrink:0} .k-col-body{flex:1;background:rgba(255,255,255,.02);border:1px solid var(–line);border-top:none;border-radius:0 0 14px 14px;padding:10px;min-height:120px;transition:background .2s} .k-col-body.drag-over{background:rgba(232,105,10,.08);border-color:var(–lineOr)} .k-card{background:rgba(255,255,255,.04);border:1px solid var(–line);border-radius:10px;padding:12px;margin-bottom:8px;cursor:grab;user-select:none;transition:all .2s;position:relative} .k-card:last-child{margin-bottom:0} .k-card:hover{border-color:var(–lineOr);background:rgba(232,105,10,.06);transform:translateY(-1px)} .k-card.dragging{opacity:.4;cursor:grabbing} .k-card-ghost{background:rgba(232,105,10,.08);border:1px dashed var(–lineOr);border-radius:10px;margin-bottom:8px} /* fresh badges */ .fresh-green{background:rgba(52,211,153,.15);color:#34D399;border:1px solid rgba(52,211,153,.3);border-radius:20px;font-size:10px;font-weight:800;padding:3px 8px} .fresh-orange{background:rgba(245,164,31,.15);color:#F5A41F;border:1px solid rgba(245,164,31,.3);border-radius:20px;font-size:10px;font-weight:800;padding:3px 8px} .fresh-red{background:rgba(255,59,48,.15);color:#FF3B30;border:1px solid rgba(255,59,48,.3);border-radius:20px;font-size:10px;font-weight:800;padding:3px 8px} .fresh-gray{background:rgba(255,255,255,.06);color:var(–t3);border:1px solid var(–line);border-radius:20px;font-size:10px;font-weight:800;padding:3px 8px} /* support */ .support-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:20px;background:rgba(96,165,250,.1);border:1px solid rgba(96,165,250,.25);color:var(–blue);font-size:11px;font-weight:700;cursor:pointer;font-family:’Inter’,sans-serif;transition:all .2s} .support-btn:hover{background:rgba(96,165,250,.18)} @media (max-width: 480px) { /* Sidebar → width 0 en flow, fixĂ©e en bas */ #sidebar { position: fixed !important; bottom: 0 !important; left: 0 !important; right: 0 !important; top: auto !important; width: 100% !important; min-width: 100% !important; max-width: 100% !important; height: 56px !important; min-height: 56px !important; max-height: 56px !important; flex-direction: row !important; display: flex !important; align-items: stretch !important; border-right: none !important; border-top: 1px solid rgba(255,255,255,.1) !important; z-index: 9999 !important; padding: 0 !important; overflow: hidden !important; background: rgba(10,10,20,.98) !important; } /* Masquer logo, crĂ©dits, infos user */ #sidebar .brand, #sidebar #sb-credits, #sidebar > div:last-child { display: none !important; } /* sb-nav → horizontal */ #sb-nav { flex: 1 !important; display: flex !important; flex-direction: row !important; width: 100% !important; height: 56px !important; overflow-x: auto !important; overflow-y: hidden !important; scrollbar-width: none !important; padding: 0 !important; gap: 0 !important; align-items: stretch !important; } #sb-nav::-webkit-scrollbar { display: none !important; } /* Boutons nav-btn */ #sb-nav .nav-btn, #sb-nav button { flex: 1 1 0 !important; min-width: 52px !important; height: 56px !important; display: flex !important; flex-direction: column !important; align-items: center !important; justify-content: center !important; gap: 2px !important; font-size: 9px !important; padding: 4px 2px !important; border-radius: 0 !important; white-space: nowrap !important; border: none !important; border-top: 2px solid transparent !important; line-height: 1.2 !important; background: transparent !important; } #sb-nav .nav-btn.active, #sb-nav button.active { color: #e8690a !important; border-top-color: #e8690a !important; } /* Main → pleine largeur, pad bas pour la barre */ #main { width: 100% !important; max-width: 100vw !important; min-width: 100% !important; padding-bottom: 64px !important; overflow-y: auto !important; overflow-x: hidden !important; box-sizing: border-box !important; height: auto !important; min-height: 100vh !important; } /* App : on laisse flex-direction:row mais la sidebar est fixed donc ne prend plus de place */ #app { overflow: hidden !important; } /* Force main Ă  prendre 100% quand sidebar est fixed */ #app { overflow: hidden !important; } #main { flex: 1 !important; width: 100vw !important; } /* Sidebar fixed ne prend plus de place dans le flow */ #sidebar { flex: 0 0 0 !important; } } /* DEBUG: fond rouge sur body quand mobile actif */ @media (max-width: 480px) { #login-screen { background: rgba(255,0,0,.05) !important; } }
Nouvel Horizon

Plateforme leads BTP · AccÚs sécurisé

var Q= »‘ »;function renderAdmins(){ var el=document.getElementById(« main »);if(!el)return; var admins=S.users.filter(function(u){return u.role=== »admin »;}); var out= »
« ; out+= »
« ; out+= »
Gestion
« ; out+= »

Administrateurs

« ; out+= »
« ; if(!admins.length){ out+= »
Aucun administrateur
« ; }else{ admins.forEach(function(a){ out+= »
« ; out+= »
« +(a.name|| »— »)+ »
« ; out+= »
✉ « +(a.email|| »— »)+ »
« ; if(a.phone)out+= »
📞 « +a.phone+ »
« ; out+= »
« ; if(a.id!==S.session.id){ out+= »« ; }else{ out+= »Vous« ; } out+= »
« ; }); } out+= »
« ; el.innerHTML=out; } function showAddAdminModal(){ var ex=document.getElementById(« aam »);if(ex)ex.remove(); var m=document.createElement(« div »);m.id= »aam »; m.style.cssText= »position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:99999;display:flex;align-items:center;justify-content:center »; var is= »width:100%;padding:11px;border-radius:12px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);color:#fff;font-size:14px;font-family:Inter,sans-serif;box-sizing:border-box;margin-top:6px »; var ls= »font-size:11px;font-weight:700;color:rgba(255,255,255,.5);text-transform:uppercase;letter-spacing:.05em »; m.innerHTML= »
 » + »
Nouvel administrateur
 » + »

Créer un compte admin

 » + »
 » + »
Nom complet *
 » + »
Email *
 » + »
Téléphone
 » + »
Mot de passe *
 » + »
 » + »
 » + » » + » » + »
« ; document.body.appendChild(m); } function submitNewAdmin(){ var name=(document.getElementById(« an-name »)?.value|| » »).trim(); var email=(document.getElementById(« an-email »)?.value|| » »).trim(); var phone=(document.getElementById(« an-phone »)?.value|| » »).trim(); var pw=(document.getElementById(« an-pw »)?.value|| » »).trim(); if(!name||!email||!pw){toast(« Nom, email et mot de passe requis », »err »);return;} if(S.users.find(function(u){return u.email===email;})){toast(« Email dĂ©jĂ  utilisé », »err »);return;} S.users.push({id: »adm »+Date.now(),name:name,email:email,phone:phone,password:pw,role: »admin »,active:true,createdAt:new Date().toISOString()}); S.logs.unshift({id: »log »+Date.now(),action: »Admin créé : « +name,user:S.session.name,ts:new Date().toISOString()}); saveToStorage(); document.getElementById(« aam »).remove(); toast(« Compte admin créé pour « +name+ » ✓ »); renderSidebar();renderPage(); } function deleteAdmin(userId){ var a=S.users.find(function(u){return u.id===userId;}); if(!a)return; if(!confirm(« Supprimer le compte admin de « +a.name+ » ? »))return; S.users=S.users.filter(function(u){return u.id!==userId;}); S.logs.unshift({id: »log »+Date.now(),action: »Admin supprimĂ© : « +a.name,user:S.session.name,ts:new Date().toISOString()}); saveToStorage();toast(a.name+ » supprimĂ© ✓ »); renderSidebar();renderPage(); } function normTrade(t){ if(!t) return  »; return t.toLowerCase() .replace(/Ă©/g,’e’).replace(/Ăš/g,’e’).replace(/ĂȘ/g,’e’).replace(/Ă«/g,’e’) .replace(/Ă /g,’a’).replace(/Ăą/g,’a’).replace(/Ă€/g,’a’) .replace(/Ăč/g,’u’).replace(/Ă»/g,’u’).replace(/ĂŒ/g,’u’) .replace(/Ăź/g,’i’).replace(/ĂŻ/g,’i’) .replace(/ĂŽ/g,’o’).replace(/ö/g,’o’) .replace(/ç/g,’c’) .trim(); } function viderCorbeille(){ var count=S.leads.filter(function(l){return !!l.deleted;}).length; if(!count){toast(« Corbeille dĂ©jĂ  vide »);return;} if(!confirm(« Supprimer dĂ©finitivement « +count+ » lead(s) ? IrrĂ©versible. »))return; S.leads=S.leads.filter(function(l){return !l.deleted;}); S.logs.unshift({id: »log »+Date.now(),action: »Corbeille vidĂ©e (« +count+ » supprimĂ©(s)) »,user:S.session.name,ts:new Date().toISOString()}); saveToStorage();toast(« Corbeille vidĂ©e ✓ »);renderSidebar();renderPage(); } // confirmReattribuer avec sync garantie function confirmReattribuer(leadId){ var sel=document.getElementById(« reassign-select »); if(!sel)return; var uid=sel.value; var u=S.users.find(function(x){return x.id===uid;}); var lead=S.leads.find(function(l){return l.id===leadId;}); if(!lead||!u)return; var oldName=lead.assignedName|| »— »; lead.assignedTo=u.id; lead.assignedName=u.name; lead.crmStatus= »nouveau »; S.logs.unshift({id: »log »+Date.now(),action: »RĂ©attribuĂ© : « +lead.name+ » (« +oldName+ » → « +u.name+ ») »,user:S.session.name,ts:new Date().toISOString()}); // Sauvegarder immĂ©diatement sur Supabase try{localStorage.setItem(« nh_data »,JSON.stringify({users:S.users,leads:S.leads,logs:S.logs,tickets:S.tickets}));}catch(e){} var SB= »https://rivjpxutbehnbrprabzb.supabase.co »; var SK= »eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJpdmpweHV0YmVobmJycHJhYnpiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU1NzcxNzcsImV4cCI6MjA5MTE1MzE3N30.wTzjmAjb9utnrgqhBboBmkD8TucUCuDhqm0l_ZW6r6A »; fetch(SB+ »/rest/v1/nh_data?id=eq.main »,{ method: »PATCH », headers:{« Content-Type »: »application/json », »apikey »:SK, »Authorization »: »Bearer « +SK, »Prefer »: »return=minimal »}, body:JSON.stringify({leads:S.leads,users:S.users,logs:S.logs,tickets:S.tickets,updated_at:new Date().toISOString()}) }); var modal=document.getElementById(« reassign-modal »);if(modal)modal.remove(); toast(« RĂ©attribuĂ© Ă  « +u.name+ » ✓ »); renderSidebar();renderPage(); } var TRADES=[« Electricien », »Installateur PV », »Installateur IRVE », »Plombier », »Chauffagiste », »Menuisier », »Peintre », »Macon », »Carreleur », »Couvreur », »Plaquiste », »Climatisation/PAC », »Autre »]; var ZONES=[« 01″, »02″, »03″, »04″, »05″, »06″, »07″, »08″, »09″, »10″, »11″, »12″, »13″, »14″, »15″, »16″, »17″, »18″, »19″, »20″, »21″, »22″, »23″, »24″, »25″, »26″, »27″, »28″, »29″, »30″, »31″, »32″, »33″, »34″, »35″, »36″, »37″, »38″, »39″, »40″, »41″, »42″, »43″, »44″, »45″, »46″, »47″, »48″, »49″, »50″, »51″, »52″, »53″, »54″, »55″, »56″, »57″, »58″, »59″, »60″, »61″, »62″, »63″, »64″, »65″, »66″, »67″, »68″, »69″, »70″, »71″, »72″, »73″, »74″, »75″, »76″, »77″, »78″, »79″, »80″, »81″, »82″, »83″, »84″, »85″, »86″, »87″, »88″, »89″, »90″, »91″, »92″, »93″, »94″, »95″, »971″, »972″, »973″, »974″, »976″]; // ─── DATA ───────────────────────────────────────────────────────────────────── const USERS_INIT = [ {id: »u1″,role: »admin »,name: »Jamal »,email: »f.nouvelhorizon@gmail.com »,password: »Kolai.jamal2025″,credits:null,active:true}, {id: »u2″,role:(document.getElementById(« nc-role »)&&document.getElementById(« nc-role »).value)|| »client »,name: »Marc Dupont »,email: »marc@elec75.fr »,password: »client123″,credits:5,trade: »Électricien »,zone: »75″,active:true,phone: »06 11 22 33 44″}, {id: »u3″,role:(document.getElementById(« nc-role »)&&document.getElementById(« nc-role »).value)|| »client »,name: »Sophie Renard »,email: »sophie@solaire13.fr »,password: »client123″,credits:12,trade: »Installateur PV »,zone: »13″,active:true,phone: »06 55 66 77 88″}, {id: »u4″,role: »client »,name: »Karim Bensaid »,email: »karim@irve69.fr »,password: »client123″,credits:3,trade: »Installateur IRVE »,zone: »69″,active:true,phone: »06 99 88 77 66″}, ]; const now = Date.now(); const h = ms => new Date(now – ms).toISOString(); const LEADS_INIT = [ {id: »l1″,name: »M. Faure »,phone: »06 12 34 56 78″,email: »faure@mail.fr »,zone: »75″,trade: »Électricien »,project: »RĂ©novation tableau Ă©lectrique »,budget: »2 500 ₏ »,createdAt:h(10*3600000),assignedTo:null,status: »pending »,crmStatus: »nouveau »,notes:[]}, {id: »l2″,name: »Mme Leblanc »,phone: »07 98 76 54 32″,email: »leblanc@mail.fr »,zone: »13″,trade: »Installateur PV »,project: »Installation 6 kWc + batterie MyLight »,budget: »12 000 ₏ »,createdAt:h(36*3600000),assignedTo: »u3″,status: »assigned »,crmStatus: »devis »,notes:[{text: »Devis envoyĂ© par email »,at:h(5*3600000)}]}, {id: »l3″,name: »M. Girard »,phone: »06 55 44 33 22″,email: »girard@mail.fr »,zone: »69″,trade: »Installateur IRVE »,project: »Borne recharge 22kW garage »,budget: »1 800 ₏ »,createdAt:h(20*3600000),assignedTo:null,status: »pending »,crmStatus: »nouveau »,notes:[]}, {id: »l4″,name: »M. Martin »,phone: »06 11 22 33 44″,email: »martin@mail.fr »,zone: »75″,trade: »Électricien »,project: »Mise aux normes NF C 15-100″,budget: »4 200 ₏ »,createdAt:h(52*3600000),assignedTo: »u2″,status: »assigned »,crmStatus: »rappel »,notes:[{text: »Premier appel — pas rĂ©pondu. »,at:h(48*3600000)}]}, {id: »l5″,name: »Mme Petit »,phone: »07 66 77 88 99″,email: »petit@mail.fr »,zone: »13″,trade: »Installateur PV »,project: »Autoconsommation 9 kWc »,budget: »15 000 ₏ »,createdAt:h(2*3600000),assignedTo:null,status: »pending »,crmStatus: »nouveau »,notes:[]}, {id: »l6″,name: »M. Rousseau »,phone: »06 33 44 55 66″,email: »rousseau@mail.fr »,zone: »75″,trade: »Électricien »,project: »VMC double flux maison »,budget: »3 800 ₏ »,createdAt:h(80*3600000),assignedTo: »u2″,status: »assigned »,crmStatus: »contacte »,notes:[{text: »RDV confirmĂ© mardi 14h. »,at:h(70*3600000)}]}, ]; // ── SUPABASE CLOUD STORAGE ────────────────────────────────────────────────── const _SB_URL = ‘https://rivjpxutbehnbrprabzb.supabase.co&rsquo;; const _SB_KEY = ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJpdmpweHV0YmVobmJycHJhYnpiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU1NzcxNzcsImV4cCI6MjA5MTE1MzE3N30.wTzjmAjb9utnrgqhBboBmkD8TucUCuDhqm0l_ZW6r6A’; const _SB_HEADERS = {‘apikey’:_SB_KEY,’Authorization’:’Bearer ‘+_SB_KEY,’Content-Type’:’application/json’}; async function loadFromCloud() { try { const r = await fetch(_SB_URL+’/rest/v1/nh_data?id=eq.main’, {headers:_SB_HEADERS}); const d = await r.json(); if(d && d[0]) { const row = d[0]; return { session:null, page: »leads », users: row.users || JSON.parse(JSON.stringify(USERS_INIT)), leads: row.leads || JSON.parse(JSON.stringify(LEADS_INIT)), logs: row.logs || [], tickets: row.tickets || [], filterStatus: »all », filterZone: »all », filterTrade: »all » }; window._cloudLoaded=true; renderSidebar(); if(S.page===’tickets’)renderPage(); } } catch(e) { console.log(‘Supabase load error:’, e); } // Fallback localStorage try { const s = localStorage.getItem(‘nh_data’); if(s) { const d=JSON.parse(s); return {session:null,page: »leads »,users:d.users||JSON.parse(JSON.stringify(USERS_INIT)),leads:(function(){ var remote = d.leads || JSON.parse(JSON.stringify(LEADS_INIT)); var local = S && S.leads ? S.leads : []; return remote.map(function(rl){ var ll = local.find(function(l){return l.id===rl.id;}); if(ll && ll.crmStatus && ll.crmStatus !== rl.crmStatus) { rl.crmStatus = ll.crmStatus; } return rl; }); })(),logs:d.logs||[],tickets:d.tickets||[],filterStatus: »all »,filterZone: »all »,filterTrade: »all »}; } } catch(e) {} return {session:null,page: »leads »,users:JSON.parse(JSON.stringify(USERS_INIT)),leads:JSON.parse(JSON.stringify(LEADS_INIT)),logs:[],tickets:[],filterStatus: »all »,filterZone: »all »,filterTrade: »all »}; } let _saveTimer = null; async function saveToCloud() { if(!S||!S.session)return; try { const body = JSON.stringify({ id:’main’, users: S.users, leads: S.leads, logs: S.logs, tickets: S.tickets, updated_at: new Date().toISOString() }); await fetch(_SB_URL+’/rest/v1/nh_data?id=eq.main’, { method:’PATCH’, headers:{…_SB_HEADERS,’Prefer’:’return=minimal’}, body }); console.log(‘Cloud saved ✓’); } catch(e) { console.log(‘Supabase save error:’, e); } } // Init depuis localStorage en attendant le cloud function saveToStorage(){ try{localStorage.setItem(« nh_data »,JSON.stringify({users:S.users,leads:S.leads,logs:S.logs,tickets:S.tickets}));}catch(e){} var SB= »https://rivjpxutbehnbrprabzb.supabase.co »; var SK= »eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJpdmpweHV0YmVobmJycHJhYnpiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU1NzcxNzcsImV4cCI6MjA5MTE1MzE3N30.wTzjmAjb9utnrgqhBboBmkD8TucUCuDhqm0l_ZW6r6A »; fetch(SB+ »/rest/v1/nh_data?id=eq.main »,{ method: »PATCH », headers:{« Content-Type »: »application/json », »apikey »:SK, »Authorization »: »Bearer « +SK, »Prefer »: »return=minimal »}, body:JSON.stringify({leads:S.leads,users:S.users,logs:S.logs,tickets:S.tickets,updated_at:new Date().toISOString()}) }).catch(function(){}); } function loadFromStorage() { try { const s = localStorage.getItem(‘nh_data’); if(s) { const d=JSON.parse(s); return {session:null,page: »leads »,users:d.users||JSON.parse(JSON.stringify(USERS_INIT)),leads:d.leads||JSON.parse(JSON.stringify(LEADS_INIT)),logs:d.logs||[],tickets:d.tickets||[],filterStatus: »all »,filterZone: »all »,filterTrade: »all »}; } } catch(e) {} return {session:null,page: »leads »,users:JSON.parse(JSON.stringify(USERS_INIT)),leads:JSON.parse(JSON.stringify(LEADS_INIT)),logs:[],tickets:[],filterStatus: »all »,filterZone: »all »,filterTrade: »all »}; } let S = loadFromStorage(); // Cloud load au dĂ©marrage dĂ©sactivĂ© — chargement au login uniquement // ─── UTILS ──────────────────────────────────────────────────────────────────── let toastTimer; function toast(txt,type= »ok »){ const t=document.getElementById(« toast »); const s={ok:{bg: »rgba(52,211,153,.1) »,b: »rgba(52,211,153,.25) »,c: »#34D399″},err:{bg: »rgba(255,59,48,.1) »,b: »rgba(255,59,48,.25) »,c: »#FF3B30″},warn:{bg: »rgba(245,164,31,.1) »,b: »rgba(245,164,31,.25) »,c: »#F5A41F »}}; const x=s[type]||s.ok; Object.assign(t.style,{background:x.bg,border: »1px solid « +x.b,color:x.c,display: »block »,boxShadow: »0 12px 40px rgba(0,0,0,.6) »}); t.textContent=(type=== »ok »? »✓ « :type=== »warn »? »⚠ « : »✕ « )+txt; clearTimeout(toastTimer); toastTimer=setTimeout(()=>{t.style.display= »none »},3500); } function fmt(iso){return new Date(iso).toLocaleDateString(« fr-FR »,{day: »2-digit »,month: »short »,hour: »2-digit »,minute: »2-digit »});} function fmtLong(iso){return new Date(iso).toLocaleDateString(« fr-FR »,{day: »2-digit »,month: »long »,year: »numeric »});} function freshBadge(createdAt){ const hrs=(Date.now()-new Date(createdAt))/3600000; if(hrs<=24) return '24h‘; if(hrs<=48) return '48h‘; if(hrs<=72) return '72h‘; return ‘+72h‘; } const CRM={ nouveau:{label: »Nouveau »,color: »var(–blue) »}, contacte:{label: »Contacté »,color: »var(–purple) »}, devis:{label: »Devis envoyé »,color: »var(–gold) »}, rappel:{label: »À rappeler »,color: »var(–or) »}, gagne:{label: »GagnĂ© ✓ »,color: »var(–green) »}, perdu:{label: »Perdu »,color: »var(–red) »}, }; function crmBadge(s){ const x=CRM[s]||{label:s,color: »var(–t3) »}; return `${x.label}`; } function ico(n,s=16){ const d={ leads:« , users:« , map:« , log:« , crm:« , stats:« , logout:« , plus:« , close:« , trash:« , edit:« , phone:« , mail:« , note:« , check:« , auto:« , chat:« , alert:« , ticket:« , inbox:« , }; return `${d[n]|| »}`; } function closeModal(id){const m=document.getElementById(id);if(m)m.remove();} // ─── AUTH ───────────────────────────────────────────────────────────────────── document.addEventListener(« keydown »,e=>{ if(e.key=== »Enter »&&document.getElementById(« login-screen »).style.display!== »none ») doLogin(); }); function showForgotPassword(){ const bg=document.createElement(« div »);bg.className= »modal-bg »;bg.id= »forgot-modal »; bg.innerHTML=’‘; bg.addEventListener(« click »,e=>{if(e.target===bg)closeModal(« forgot-modal »);}); document.body.appendChild(bg); } function submitForgotPassword(){ const email=document.getElementById(« forgot-email »).value.trim(); if(!email){toast(« Entrez votre email », »err »);return;} closeModal(« forgot-modal »); toast(« Demande envoyĂ©e, l\’admin vous recontactera sous 24h », »warn »); const u=S.users.find(x=>x.email===email&&x.active!==false); if(u){ const sub=encodeURIComponent(« RĂ©initialisation mot de passe – « +u.name); const bod=encodeURIComponent(« Bonjour,\n\n »+u.name+ » (« +email+ ») demande une rĂ©initialisation.\nMot de passe actuel : « +u.password+ »\n\nNouvel Horizon »); window.open(« mailto:f.nouvelhorizon@gmail.com?subject= »+sub+ »&body= »+bod, »_blank »); } } function checkMustChangePw(){ if(S.session&&S.session.mustChangePassword) showChangePwModal(true); } function showChangePwModal(forced){ const bg=document.createElement(« div »);bg.className= »modal-bg »;bg.id= »change-pw-modal »; const oldPwField=forced? » »:’
Mot de passe actuel
‘; const cancelBtn=forced? » »:’‘; const warning=forced?’

⚠ Changez votre mot de passe provisoire avant de continuer.

‘: » »; bg.innerHTML=’‘; if(!forced) bg.addEventListener(« click »,e=>{if(e.target===bg)closeModal(« change-pw-modal »);}); document.body.appendChild(bg); } function submitChangePw(forced){ const np=document.getElementById(« cpw-new »).value; const cf=document.getElementById(« cpw-confirm »).value; if(!forced){const op=document.getElementById(« cpw-old »).value;if(op!==S.session.password){toast(« Mot de passe actuel incorrect », »err »);return;}} if(np.lengthx.id===S.session.id); if(u){u.password=np;u.mustChangePassword=false;} S.session.password=np;S.session.mustChangePassword=false; saveToStorage();closeModal(« change-pw-modal »);toast(« Mot de passe changĂ© ✓ »); } function unassignLead(leadId){ var lead=S.leads.find(function(l){return l.id===leadId;}); if(!lead)return; if(!confirm(« DĂ©sattribuer ce lead ? »))return; var oldName=lead.assignedName|| »cet artisan »; lead.assignedTo=null;lead.assignedName=null;lead.crmStatus=null; S.logs.unshift({id: »log »+Date.now(),action: »Lead dĂ©sattribuĂ© : « +lead.name+ » (Ă©tait chez « +oldName+ ») »,user:S.session.name,ts:new Date().toISOString()}); saveToStorage();toast(« Lead dĂ©sattribuĂ© ✓ »);renderSidebar();renderPage(); } function trashLead(leadId){ var lead=S.leads.find(function(l){return l.id===leadId;}); if(!lead)return; if(!confirm(« Mettre en corbeille ? »))return; lead.deleted=true;lead.deletedBy=S.session.name;lead.deletedAt=new Date().toISOString(); lead.assignedTo=null;lead.assignedName=null; S.logs.unshift({id: »log »+Date.now(),action: »Lead en corbeille : « +lead.name,user:S.session.name,ts:new Date().toISOString()}); saveToStorage();toast(« Mis en corbeille ✓ »);renderSidebar();renderPage(); } function restoreLead(leadId){ var lead=S.leads.find(function(l){return l.id===leadId;}); if(!lead)return; delete lead.deleted;delete lead.deletedBy;delete lead.deletedAt; S.logs.unshift({id: »log »+Date.now(),action: »Lead restaurĂ© : « +lead.name,user:S.session.name,ts:new Date().toISOString()}); saveToStorage();toast(« Lead restaurĂ© ✓ »);renderSidebar();renderPage(); } function clientClaimLead(leadId){ var u=S.users.find(function(x){return x.id===S.session.id;}); if(!u||(u.credits||0)<=0){toast("Credits insuffisants","err");return;} var lead=S.leads.find(function(l){return l.id===leadId;}); if(!lead||lead.assignedTo)return; u.credits=Math.max(0,(u.credits||0)-1);S.session.credits=u.credits; lead.assignedTo=S.session.id;lead.assignedName=S.session.name;lead.crmStatus="nouveau"; S.logs.unshift({id:"log"+Date.now(),action:"Contact obtenu par "+S.session.name,user:S.session.name,ts:new Date().toISOString()}); saveToStorage();toast("Contact obtenu ✓");renderSidebar();renderPage(); } function updateLeadStatus(leadId,status){ var lead=S.leads.find(function(l){return l.id===leadId;}); if(lead){lead.crmStatus=status;saveToStorage();saveToCloud(S);toast("Statut mis Ă  jour");} } function deleteArtisan(userId){ var u=S.users.find(function(x){return x.id===userId;}); if(!u)return; if(!confirm("Supprimer "+u.name+" ?"))return; S.users=S.users.filter(function(x){return x.id!==userId;}); S.logs.unshift({id:"log"+Date.now(),action:"Artisan supprimĂ© : "+u.name,user:S.session.name,ts:new Date().toISOString()}); saveToStorage();toast(u.name+" supprimĂ©");renderSidebar();renderPage(); } function reattribuerLead(leadId){ var lead=S.leads.find(function(l){return l.id===leadId;}); if(!lead)return; var artisans=S.users.filter(function(u){return u.role==="client"&&!u.suspended;}); if(!artisans.length){toast("Aucun artisan disponible","err");return;} var opts=artisans.map(function(a){ var sel=a.id===lead.assignedTo?" selected":""; return " »+a.name+ » (« +( a.trade|| »? »)+ ») Zone « +(a.zone|| »? »)+ » »; }).join(«  »); var ex=document.getElementById(« reassign-modal »);if(ex)ex.remove(); var m=document.createElement(« div »); m.id= »reassign-modal »; m.style.cssText= »position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:99999;display:flex;align-items:center;justify-content:center »; m.innerHTML= »
 » + »
Réattribuer
 » + »

« +(lead.name|| »— »)+ »

 » + »
Choisir un artisan
 » + » »+opts+ » » + »
 » + » » + » » + »
« ; document.body.appendChild(m); } function confirmReattribuer(leadId){ var sel=document.getElementById(« reassign-select »); if(!sel)return; var uid=sel.value; var u=S.users.find(function(x){return x.id===uid;}); var lead=S.leads.find(function(l){return l.id===leadId;}); if(!lead||!u)return; var oldName=lead.assignedName|| »— »; lead.assignedTo=u.id;lead.assignedName=u.name;lead.crmStatus= »nouveau »; S.logs.unshift({id: »log »+Date.now(),action: »RĂ©attribuĂ© : « +lead.name+ » (« +oldName+ » → « +u.name+ ») »,user:S.session.name,ts:new Date().toISOString()}); saveToStorage(); document.getElementById(« reassign-modal »).remove(); toast(« RĂ©attribuĂ© Ă  « +u.name+ » ✓ »); renderSidebar();renderPage(); } async function doLogin(){ // Charger donnĂ©es fraĂźches depuis Supabase avant de valider try { const loaded = await loadFromCloud(); S.users = loaded.users; S.leads = loaded.leads; S.logs = loaded.logs; S.tickets = loaded.tickets; } catch(e) { console.log(‘Cloud load at login failed, using cache’); } const email=document.getElementById(« l-email »).value.trim(); const pw=document.getElementById(« l-pw »).value; const u=S.users.find(x=>x.email===email&&x.password===pw&&x.active!==false); const err=document.getElementById(« l-err »); if(!u){err.style.display= »block »;err.textContent=S.users.find(x=>x.email===email)? »Compte suspendu — contactez l’administrateur »: »Email ou mot de passe incorrect »;return;} err.style.display= »none »; S.session=u; document.getElementById(« login-screen »).style.display= »none »; document.getElementById(« app »).style.display= »flex »; startNhPoll(); renderSidebar();renderPage(); setTimeout(checkMustChangePw,300); } // ── Auto-refresh Supabase toutes les 10 secondes ──────────────────────────── var _nhPollInterval = null; function startNhPoll(){ if(_nhPollInterval) clearInterval(_nhPollInterval); _nhPollInterval = setInterval(function(){ if(!S || !S.session) return; var SB= »https://rivjpxutbehnbrprabzb.supabase.co »; var SK= »eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJpdmpweHV0YmVobmJycHJhYnpiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU1NzcxNzcsImV4cCI6MjA5MTE1MzE3N30.wTzjmAjb9utnrgqhBboBmkD8TucUCuDhqm0l_ZW6r6A »; fetch(SB+ »/rest/v1/nh_data?id=eq.main »,{ headers:{« apikey »:SK, »Authorization »: »Bearer « +SK} }).then(function(r){return r.json();}).then(function(data){ if(!data||!data[0]) return; var remote = data[0]; var leadsChanged = JSON.stringify(remote.leads) !== JSON.stringify(S.leads); var usersChanged = JSON.stringify(remote.users) !== JSON.stringify(S.users); if(leadsChanged || usersChanged){ S.leads = remote.leads || S.leads; S.users = remote.users || S.users; S.logs = remote.logs || S.logs; S.tickets = remote.tickets || S.tickets; // Mettre Ă  jour les crĂ©dits de la session var me = S.users.find(function(u){return u.id===S.session.id;}); if(me) S.session.credits = me.credits; renderSidebar(); renderPage(); } if(S.page===’tickets’){var _tc=document.getElementById(‘tickets-container’);if(!_tc||!_tc.innerHTML.trim())renderPage();} }).catch(function(){}); }, 10000); } function doLogout(){ S.session=null; document.getElementById(« app »).style.display= »none »; document.getElementById(« login-screen »).style.display= »flex »; document.getElementById(« l-email »).value= » »; document.getElementById(« l-pw »).value= » »; } // ─── SIDEBAR ───────────────────────────────────────────────────────────────── function renderSidebar(){ const s=S.session; document.getElementById(« sb-role »).textContent=s.role=== »admin »? »Administrateur »: »Artisan »; document.getElementById(« sb-name »).textContent=s.name; const pending=S.leads.filter(l=>l.status=== »pending »).length; const myLeads=S.leads.filter(l=>l.assignedTo===s.id).length; const rappel=S.leads.filter(l=>l.assignedTo===s.id&&l.crmStatus=== »rappel »).length; const openTickets=S.tickets.filter(t=>t.status=== »open »).length; const adminNav=[ {id: »leads »,label: »Leads entrants »,icon: »leads »,badge:pending||0}, {id: »clients »,label: »Artisans & crĂ©dits »,icon: »users »}, {id: »admins »,label: »Administrateurs »,icon: »admin »}, {id: »stats »,label: »Statistiques »,icon: »stats »}, {id: »logs »,label: »Journal »,icon: »log »}, ]; const clientNav=[ {id: »leads »,label: »Leads disponibles »,icon: »map »}, {id: »crm »,label: »Mon CRM »,icon: »crm »,badge:rappel||0}, {id: »my-leads »,label: »Mes contacts »,icon: »leads »,badge:myLeads||0}, ]; const nav=s.role=== »admin »?adminNav:clientNav; document.getElementById(« sb-nav »).innerHTML=nav.map(item=>` `).join(«  »); const cw=document.getElementById(« sb-credits »); const sb=document.getElementById(« support-sidebar-btn »); if(s.role=== »client »){ cw.style.display= »block »; const me=S.users.find(u=>u.id===s.id)||s; const n=me.credits||0; document.getElementById(« sb-credit-n »).textContent=n; document.getElementById(« sb-credit-n »).style.color=n>0? »var(–or) »: »var(–red) »; document.getElementById(« sb-credit-warn »).style.display=n===0? »block »: »none »; sb.style.display= »flex »; } else { cw.style.display= »none »; sb.style.display= »none »; } } function setPage(p){S.page=p;renderSidebar();renderPage();} // ─── ROUTER ─────────────────────────────────────────────────────────────────── function renderPage(){ const m=document.getElementById(« main »); if(S.page===’admins’&&S.session&&S.session.role===’admin’){renderAdmins();return;} if(S.page=== »leads ») if(S.session&&S.session.role===’admin’){renderLeads(m);}else{renderMyLeads();} else if(S.page=== »clients ») renderClients(m); else if(S.page=== »logs ») renderLogs(m); else if(S.page=== »stats ») renderStats(m); else if(S.page=== »crm ») renderCRM(m); else if(S.page=== »my-leads ») renderMyLeads(m); else if(S.page=== »support ») renderSupport(m); else if(S.page=== »tickets »||S.page=== »support »){ if(S.session&&S.session.role!== »admin »){renderSupport(m);} else{m.innerHTML=’
📧

Tickets support

Les tickets sont gérés par email.

‘;} } } // ─── LEADS ─────────────────────────────────────────────────────────────────── function renderLeads(el){ const s=S.session; const zones=[…new Set(S.leads.map(l=>l.zone))].sort(); const trades=[…new Set(S.leads.map(l=>l.trade))].sort(); let filtered=S.leads.filter(l=>{ if(s.role!== »admin ») return l.assignedTo===s.id && !l.deleted; const zf=document.getElementById(« f-zone »)?.value|| » »; const tf=document.getElementById(« f-trade »)?.value|| » »; const tab=document.getElementById(« f-tab »)?.value|| »all »; if(l.deleted) return false; if(zf && l.zone!==zf) return false; if(tf && l.trade!==tf) return false; if(tab=== »pending ») return !l.assignedTo; if(tab=== »assigned ») return !!l.assignedTo; return true; }); const trashed=s.role=== »admin » ? S.leads.filter(l=>l.deleted) : []; const pending=S.leads.filter(l=>!l.assignedTo && !l.deleted).length; const zoneOpts=zones.map(z=>`${z}`).join(«  »); const tradeOpts=trades.map(t=>`${t}`).join(«  »); let out=`
Leads entrants

${S.leads.filter(l=>!l.deleted).length} contacts ${pending>0?’‘+pending+’ en attente‘: »}

${s.role=== »admin »?`
`: »}
${s.role=== »admin »?`
Toutes zones${zoneOpts} Tous métiers${tradeOpts}
`: »}
${filtered.map(l=>leadCard(l,S)).join(«  »)||’
Aucun lead
‘}
${trashed.length>0?`
đŸ—‘ïž Corbeille (${trashed.length})
${trashed.map(tl=>`
${tl.name|| »— »}
SupprimĂ© par ${tl.deletedBy|| »— »}
`).join(«  »)}
`: »} `; el.innerHTML=out; } function leadCard(lead){ const s=S.session; const aUser=S.users.find(u=>u.id===lead.assignedTo); const elig=S.users.filter(u=>u.role=== »client »&&normTrade(u.trade)===normTrade(lead.trade)&&String(u.zone).substring(0,2)===String(lead.zone).substring(0,2)&&u.credits>0&&u.active!==false); const isA=lead.status=== »assigned »; const isMine=lead.assignedTo===s.id; const reveal=s.role=== »admin »||isMine; let action= » »; if(s.role=== »admin »){ if(isA){ action=`
${ico(« check »,13)} AttribuĂ© Ă  ${aUser?aUser.name: »— »}
`; } else { const opts=S.users.filter(u=>u.role=== »client »&&u.active!==false).map(u=>`<option value="${u.id}" ${u.credits${u.name} — ${u.credits} crĂ©dit${u.credits!==1? »s »: » »}${normTrade(u.trade)===normTrade(lead.trade)&&String(u.zone).substring(0,2)===String(lead.zone).substring(0,2)? » ✓ »: » »}`).join(«  »); action=`
${lead.assignedTo ? `` :  »} ${elig.length>0?``: » »}
${elig.length===0?`

⚠ Aucun artisan Ă©ligible

`: » »} `; } } // Boutons admin : corbeille + dĂ©sattribuer var trashBtn2 = S.session && S.session.role === « admin » ? ‘‘ : «  »; var unassignBtn2 = (S.session && S.session.role === « admin » && lead.assignedTo) ? ‘‘ : «  »; var reassignBtn = (lead.assignedTo && s.role === « admin ») ? ‘‘ : «  »; const reclamBtn=isMine?``: » »; return `
${freshBadge(lead.createdAt)}${isA?crmBadge(lead.crmStatus): » »}
${reveal?lead.name:`Contact masqué`}
${s.role=== »admin »&&!isA?``: » »}
${lead.trade} Zone ${lead.zone}
${lead.project}
${reveal?`
${ico(« phone »,12)} ${lead.phone}
${ico(« mail »,12)} ${lead.email}
Budget : ${lead.budget}
${lead.comment ? `
${lead.comment}
` : «  »}${lead.source && lead.source !== « Borne de recharge France » ? `
${lead.source}
` : «  »}`:`
Coordonnées visibles aprÚs attribution
`}
${fmt(lead.createdAt)}
${action}${reclamBtn}${reassignBtn}${trashBtn2}${unassignBtn2}
`; } function showManual(id){document.getElementById(« abtns-« +id).style.display= »none »;document.getElementById(« manual-« +id).style.display= »block »;} function setFilter(v){S.filterStatus=v;renderPage();} function setZone(v){S.filterZone=v;renderPage();} function setTrade(v){S.filterTrade=v;renderPage();} // ─── CRM KANBAN ─────────────────────────────────────────────────────────────── let dragLeadId=null, dragFromStatus=null; function renderCRM(el){ const s=S.session; const myLeads=S.leads.filter(l=>l.assignedTo===s.id); const cols=Object.entries(CRM); const statRow=cols.map(([k,x])=>{ const n=myLeads.filter(l=>l.crmStatus===k).length; return `
${n}
${x.label}
`; }).join(«  »); const kanban=cols.map(([k,x])=>{ const colLeads=myLeads.filter(l=>l.crmStatus===k); const cards=colLeads.map(l=>`
${l.name}
${l.project}
${l.budget} ${freshBadge(l.createdAt)}
${l.notes&&l.notes.length?`
${ico(« note »,10)} ${l.notes.length} note${l.notes.length>1? »s »: » »}
`: » »}
`).join(«  ») || `
Vide
`; return `
${x.label} ${colLeads.length}
${cards}
`; }).join(«  »); el.innerHTML=`
Suivi commercial
Mon CRM

${myLeads.length} contact${myLeads.length!==1? »s »: » »} · glisser-déposer pour changer le statut

${statRow}
${kanban}

Glissez une carte pour changer son statut · Cliquez pour ouvrir la fiche

`; } // ─── DRAG & DROP ────────────────────────────────────────────────────────────── function onDragStart(e, leadId, fromStatus){ dragLeadId=leadId; dragFromStatus=fromStatus; e.dataTransfer.effectAllowed= »move »; setTimeout(()=>{if(e.target)e.target.classList.add(« dragging »);},0); } function onDragEnd(e){ dragLeadId=null; dragFromStatus=null; document.querySelectorAll(« .k-card »).forEach(c=>c.classList.remove(« dragging »)); document.querySelectorAll(« .k-col-body »).forEach(c=>c.classList.remove(« drag-over »)); } function onDragOver(e, toStatus){ e.preventDefault(); e.dataTransfer.dropEffect= »move »; const col=document.getElementById(« col-« +toStatus); if(col) col.classList.add(« drag-over »); } function onDragLeave(e, toStatus){ const col=document.getElementById(« col-« +toStatus); if(col) col.classList.remove(« drag-over »); } function onDrop(e, toStatus){ e.preventDefault(); const col=document.getElementById(« col-« +toStatus); if(col) col.classList.remove(« drag-over »); if(!dragLeadId||toStatus===dragFromStatus) return; const lead=S.leads.find(l=>l.id===dragLeadId); if(!lead) return; const old=lead.crmStatus; lead.crmStatus=toStatus; S.logs.unshift({id:Date.now(),action:`${lead.name} : ${CRM[old]?.label||old} → ${CRM[toStatus]?.label||toStatus}`,at:new Date().toISOString(),type: »crm »}); toast(`${lead.name} → ${CRM[toStatus]?.label||toStatus}`); saveToCloud(S);renderSidebar(); renderPage(); } // ─── LEAD DETAIL MODAL ──────────────────────────────────────────────────────── function openLeadDetail(leadId){ const lead=S.leads.find(l=>l.id===leadId); if(!lead) return; const statusOpts=Object.entries(CRM).map(([k,x])=>`${x.label}`).join(«  »); const notesHtml=lead.notes&&lead.notes.length ?lead.notes.slice().reverse().map((n,ri)=>`
${n.text}
${fmt(n.at)}
`).join(«  ») :`
Aucune note
`; const isPerdu=lead.crmStatus=== »perdu »; const bg=document.createElement(« div »); bg.className= »modal-bg »; bg.id= »lead-detail-modal »; bg.innerHTML=` `; bg.addEventListener(« click »,e=>{if(e.target===bg)closeModal(« lead-detail-modal »);}); document.body.appendChild(bg); } function updateCRMStatus(leadId,status){ const lead=S.leads.find(l=>l.id===leadId); if(!lead) return; const old=lead.crmStatus; lead.crmStatus=status; S.logs.unshift({id:Date.now(),action:`${lead.name} : ${CRM[old]?.label||old} → ${CRM[status]?.label||status}`,at:new Date().toISOString(),type: »crm »}); toast(« Statut : « +CRM[status]?.label||status); // Refresh modal to update delete button if needed closeModal(« lead-detail-modal »); openLeadDetail(leadId); } function addNote(leadId){ const inp=document.getElementById(« new-note-« +leadId); const text=inp.value.trim(); if(!text){toast(« Note vide », »err »);return;} const lead=S.leads.find(l=>l.id===leadId); if(!lead) return; if(!lead.notes) lead.notes=[]; lead.notes.push({text,at:new Date().toISOString()}); S.logs.unshift({id:Date.now(),action:`Note ajoutĂ©e — ${lead.name}`,at:new Date().toISOString(),type: »note »}); toast(« Note enregistrĂ©e »); closeModal(« lead-detail-modal »); openLeadDetail(leadId); } function deleteNote(leadId,idx){ const lead=S.leads.find(l=>l.id===leadId); if(!lead||!lead.notes) return; lead.notes.splice(idx,1); toast(« Note supprimĂ©e », »warn »); closeModal(« lead-detail-modal »); openLeadDetail(leadId); } function deleteLeadPermanent(leadId){ S.leads=S.leads.filter(l=>l.id!==leadId); toast(« Lead supprimé », »warn »); closeModal(« lead-detail-modal »); renderSidebar();renderPage(); } // ─── SUPPORT / RÉCLAMATION ──────────────────────────────────────────────────── function openSupport(leadId){ if(!S.session){toast(« Connectez-vous d’abord », »err »);return;} S.page=’support’; renderSidebar(); renderPage(); } async function submitSupportMessage() { const name = document.getElementById(‘sup-name’)?.value?.trim(); const email = document.getElementById(‘sup-email’)?.value?.trim(); const message = document.getElementById(‘sup-message’)?.value?.trim(); if (!name || !email || !message) { alert(‘Merci de remplir tous les champs.’); return; } const btn = document.querySelector(‘#modal button[onclick= »submitSupportMessage() »]’); if (btn) { btn.textContent = ‘Envoi…’; btn.disabled = true; } try { const res = await fetch(‘https://formspree.io/f/xeepbayl&rsquo;, { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’, ‘Accept’: ‘application/json’ }, body: JSON.stringify({ _subject: ‘Support NH Lead Manager – ‘ + name, nom: name, email: email, message: message, _replyto: email }) }); if (res.ok) { const conf = document.getElementById(‘sup-confirm’); if (conf) conf.style.display = ‘block’; document.getElementById(‘sup-name’).value =  »; document.getElementById(‘sup-email’).value =  »; document.getElementById(‘sup-message’).value =  »; } else { alert(‘Erreur lors de l\’envoi. RĂ©essayez.’); } } catch(e) { alert(‘Erreur rĂ©seau. RĂ©essayez.’); } if (btn) { btn.textContent = ‘Envoyer au support’; btn.disabled = false; } } // ─── SUPPORT ADMIN (tickets) ────────────────────────────────────────────────── function renderTickets(m){ var el=document.getElementById(‘tickets-container’); if(!el){ if(!m)m=document.getElementById(‘main’); if(!m)return; m.innerHTML=’

Support

‘; el=document.getElementById(‘tickets-container’); if(!el)return; } var isAdmin=S.session&&S.session.role===’admin’; var tickets=(S.tickets||[]).filter(function(t){ if(isAdmin)return true; return t.userId===S.session.id&&t.status!==’closed’; }); if(!tickets.length){ el.innerHTML=’

Aucun ticket en cours

‘; return; } el.innerHTML=tickets.map(function(t){ var replies=t.replies||[]; var sc=t.status===’closed’?’#4ade80′:’#f5a41f’; var sl=t.status===’closed’?’Resolu’:’Ouvert’; var ini=(t.userName||’A’); var hist=’
‘; hist+=’
‘; hist+=’
‘+ini[0].toUpperCase()+’
‘; hist+=’
‘+ini+’
‘; hist+=’
‘+t.message+’
‘; replies.forEach(function(r){ var adm=r.role===’admin’; var dir=adm?’flex-direction:row-reverse’: »; var ta=adm?’text-align:right’: »; var bg=adm?’rgba(26,36,114,.45)’:’rgba(255,255,255,.07)’; var br=adm?’10px 0 10px 10px’:’0 10px 10px 10px’; var bd=adm?’rgba(100,130,255,.3)’:’rgba(255,255,255,.1)’; var abg=adm?’#1a2472′:’#374151′; hist+=’
‘; hist+=’
‘+((r.author||’?’)[0].toUpperCase())+’
‘; hist+=’
‘+(r.author||’Support’)+’
‘; hist+=’
‘+r.text+’
‘; }); hist+=’
‘; var zone= »; if(t.status!==’closed’){ if(isAdmin||t.userId===S.session.id){ zone+=’
‘; zone+= »; zone+=’‘; zone+=’
‘; } if(isAdmin){ zone+=’‘; } } else { zone=’
Ticket resolu
‘; } return ‘
‘ +’
‘ +’‘+t.subject+’‘ +’‘+sl+’‘ +’
‘ +’
‘+replies.length+’ message’+(replies.length>1?’s’: »)+’
‘ +hist+zone+’
‘; }).join( »); } function closeTicket(ticketId){ var t=(S.tickets||[]).find(function(x){return x.id===ticketId;}); if(!t)return; t.status=’closed’;t.closedAt=new Date().toISOString(); S.logs.unshift({id:’log_’+Date.now(),action:’Ticket resolu : ‘+t.subject,user:S.session.name,ts:new Date().toISOString()}); saveToCloud(S);toast(‘Ticket marque comme resolu’);renderSidebar();renderPage(); } function replyTicket(ticketId){ var t=(S.tickets||[]).find(function(x){return x.id===ticketId;}); if(!t)return; var inp=document.getElementById(‘rp_’+ticketId); if(!inp)return; var txt=(inp.value|| »).trim(); if(!txt)return; if(!t.replies)t.replies=[]; t.replies.push({id:’r_’+Date.now(),text:txt,author:S.session.name,role:S.session.role,userId:S.session.id,at:new Date().toISOString()}); S.logs.unshift({id:’log_’+Date.now(),action:’Message support : ‘+t.subject,user:S.session.name,ts:new Date().toISOString()}); saveToCloud(S); var subj=S.session.role===’admin’?'[NH Support] Reponse admin – ‘+t.subject:'[NH Support] Message artisan – ‘+t.subject; fetch(‘https://formspree.io/f/xeepbayl&rsquo;,{method:’POST’,headers:{‘Content-Type’:’application/json’},body:JSON.stringify({_subject:subj,message:txt,artisan:S.session.name,ticket:t.subject})}).catch(function(){}); toast(‘Message envoye’);renderSidebar();if(S.page===’tickets’)renderTickets(document.getElementById(‘main’));else renderPage(); } function renderMyLeads(){ var myLeads = S.leads.filter(function(l){ return l.assignedTo === S.session.id && l.crmStatus !== « gagne » && l.crmStatus !== « perdu »; }); var archived = S.leads.filter(function(l){ return l.assignedTo === S.session.id && (l.crmStatus === « gagne » || l.crmStatus === « perdu »); }); var avail = []; // Leads disponibles masques cote artisan — visibles apres attribution uniquement var credits = S.session.credits || 0; var out = «  »; // Leads disponibles if(avail.length > 0){ out += « 
Disponibles ( » + avail.length + « )
« ; avail.forEach(function(lead){ var tradeSpan = «  » + (lead.trade|| »— ») + « « ; var zoneSpan = « ZONE  » + (lead.zone|| »— ») + « « ; var claimBtn = credits > 0 ? «  » : « 
Credits insuffisants — contactez le support
« ; out += « 
« ; out += « 
« ; out += « 
Nom confidentiel
« ; out += « 
 » + tradeSpan + zoneSpan + « 
« ; out += «  » + (lead.age|| »— ») + « h
« ; out += « 
 » + (lead.project|| » ») + « 
« ; out += « 
📍 Coordonnees visibles apres attribution
« ; out += claimBtn + « 
« ; }); } // Leads attribues — coordonnees visibles + bouton CRM if(myLeads.length > 0){ out += « 
Mes contacts ( » + myLeads.length + « )
« ; myLeads.forEach(function(lead){ var sc = lead.crmStatus === « gagne » ? « #22c55e » : lead.crmStatus === « perdu » ? « #ef4444 » : lead.crmStatus === « contacte » ? « #3b82f6 » : « var(–or) »; var tradeSpan = «  » + (lead.trade|| »— ») + « « ; var zoneSpan = « ZONE  » + (lead.zone|| »— ») + « « ; var budgetLine = lead.budget ? « 
Budget :  » + lead.budget + « 
 » : «  »; var statusSel = «  »; statusSel += « Nouveau »; statusSel += « Contacte »; statusSel += « Gagne »; statusSel += « Perdu »; statusSel += «  »; var crmBtn = « « ; out += « 
« ; out += « 
« ; out += « 
 » + (lead.name|| »Contact ») + « 
« ; out += «  » + (lead.crmStatus|| »nouveau »).toUpperCase() + « 
« ; out += « 
 » + tradeSpan + zoneSpan + « 
« ; out += « 
 » + (lead.project|| » ») + « 
« ; out += « 
« ; out += « 
📞  » + (lead.phone|| »— ») + « 
« ; out += « 
✉  » + (lead.email|| »— ») + « 
« ; out += budgetLine + « 
« ; out += « 
 » + crmBtn + statusSel + « 
« ; out += « 
« ; }); } if(!avail.length && !myLeads.length){ out = « 
Aucun contact disponible pour le moment.
« ; } document.getElementById(« main »).innerHTML = « 
 » + « 
Leads disponibles
 » + « 

 » + myLeads.length +  » contact » + (myLeads.length !== 1 ? « s » : «  ») +  » attribue » + (myLeads.length !== 1 ? « s » : «  ») + « 

 » + out + « 
« ; } function renderClients(el){ const clients=S.users.filter(function(u){return u.role!== »admin »;}).filter(u=>u.role=== »client »); const cards=clients.map(c=>{ const cl=S.leads.filter(l=>l.assignedTo===c.id).length; const gagne=S.leads.filter(l=>l.assignedTo===c.id&&l.crmStatus=== »gagne »).length; const low=c.credits<=2; const inactive=c.active===false; return `
${c.name}
${inactive?`Suspendu`: » »}
${c.email}
${c.trade} Zone ${c.zone}
${c.credits}
crédit${c.credits!==1? »s »: » »}
${cl}
reçus
0? »var(–green) »: »var(–red) »} »>${inactive? »OFF »:c.credits>0? »Actif »: »Épuisé »}
statut
${gagne}
gagnés
Crédits
${[10,30,60].map(n=>``).join(«  »)}
${[5,10].map(n=>``).join(«  »)}
`; }).join(«  »); el.innerHTML=`
Gestion abonnements
Artisans & Crédits

${clients.length} artisans · 1 crédit = 1 contact exclusif

${cards}
`; } // ─── STATS ──────────────────────────────────────────────────────────────────── function renderStats(el){ const total=S.leads.length, clients=S.users.filter(u=>u.role=== »client »); const pending=S.leads.filter(l=>l.status=== »pending »).length; const assigned=S.leads.filter(l=>l.status=== »assigned »).length; const gagne=S.leads.filter(l=>l.crmStatus=== »gagne »).length; const perdu=S.leads.filter(l=>l.crmStatus=== »perdu »).length; const totalCredits=clients.reduce((a,c)=>a+(c.credits||0),0); const trades=[…new Set(S.leads.map(l=>l.trade))].map(t=>({t,n:S.leads.filter(l=>l.trade===t).length})).sort((a,b)=>b.n-a.n); el.innerHTML=`
Vue d’ensemble
Statistiques
${[[« Total leads »,total, »var(–t) »],[« En attente »,pending, »var(–gold) »],[« AttribuĂ©s »,assigned, »var(–or) »],[« GagnĂ©s »,gagne, »var(–green) »],[« Perdus »,perdu, »var(–red) »],[« CrĂ©dits actifs »,totalCredits, »var(–blue) »],[« Artisans »,clients.length, »var(–purple) »],[« Taux attribution »,total>0?Math.round(assigned/total*100)+ »% »: »0% », »var(–or) »],[« Tickets ouverts »,S.tickets.filter(t=>t.status=== »open »).length, »var(–blue) »]].map(([l,v,c])=>`
${v}
${l}
`).join(«  »)}
Répartition par métier
${trades.map(({t,n})=>{const pct=Math.round(n/total*100)||0;return`
${t}${n} (${pct}%)
`;}).join(«  »)}
Performance artisans
${clients.map(c=>{const tot=S.leads.filter(l=>l.assignedTo===c.id).length;const g=S.leads.filter(l=>l.assignedTo===c.id&&l.crmStatus=== »gagne »).length;const tx=tot>0?Math.round(g/tot*100):0;return`
${c.name}
${c.trade} · Zone ${c.zone}
20? »var(–green) »:tx>10? »var(–gold) »: »var(–t) »} »>${tx}%
${g}/${tot}
`;}).join(«  »)}
`; } // ─── LOGS ───────────────────────────────────────────────────────────────────── function renderLogs(el){ const dot={assign: »var(–or) »,auto: »var(–green) »,credits: »var(–gold) »,crm: »var(–blue) »,note: »var(–purple) »,ticket: »var(–blue) »}; const rows=S.logs.length?S.logs.map((l,i)=>`
${l.action}
${fmt(l.at)}
`).join(«  »):`
Aucune activité
`; el.innerHTML=`
Traçabilité
Journal d’activitĂ©

${S.logs.length} événement${S.logs.length!==1? »s »: » »}

${rows}
`; } // ─── ACTIONS ────────────────────────────────────────────────────────────────── function assignLead(leadId,clientId){ const client=S.users.find(u=>u.id===clientId); const lead=S.leads.find(l=>l.id===leadId); if(!client||!lead) return; if(client.creditsl.status=== »pending »)){ const m=S.users.find(u=>u.role=== »client »&&normTrade(u.trade)===normTrade(lead.trade)&&String(u.zone).substring(0,2)===String(lead.zone).substring(0,2)&&u.credits>0&&u.active!==false); if(m){m.credits–;lead.assignedTo=m.id;lead.status= »assigned »;if(!lead.crmStatus)lead.crmStatus= »nouveau »;S.logs.unshift({id:Date.now()+count,action:`AUTO — ${lead.name} → ${m.name} (−1 crĂ©dit)`,at:new Date().toISOString(),type: »auto »});count++;} } toast(count>0?count+ » contact(s) distribuĂ©s »: »Aucun match »,count>0? »ok »: »warn »); renderSidebar();renderPage(); } function addCredits(userId,amount){const u=S.users.find(x=>x.id===userId);if(!u)return;u.credits+=amount;S.logs.unshift({id:Date.now(),action:`+${amount} crĂ©dit(s) → ${u.name}`,at:new Date().toISOString(),type: »credits »});toast(`+${amount} crĂ©dits → ${u.name}`);renderSidebar();renderPage();} function removeCredits(userId,amount){const u=S.users.find(x=>x.id===userId);if(!u)return;if(u.creditsx.id===userId);if(!u)return;u.active=!u.active;S.logs.unshift({id:Date.now(),action:`AccĂšs ${u.active? »rĂ©activé »: »suspendu »} : ${u.name}`,at:new Date().toISOString(),type: »credits »});toast(u.active? »AccĂšs rĂ©activé »: »AccĂšs suspendu »,u.active? »ok »: »warn »);renderSidebar();renderPage();} function deleteLead(id){if(!confirm(« Supprimer ce lead ? »))return;S.leads=S.leads.filter(l=>l.id!==id);toast(« Lead supprimé », »warn »);renderSidebar();renderPage();} // ─── MODALS ─────────────────────────────────────────────────────────────────── function addClientModal(){ const bg=document.createElement(« div »);bg.className= »modal-bg »;bg.id= »add-client-modal »; bg.innerHTML=``; bg.addEventListener(« click »,e=>{if(e.target===bg)closeModal(« add-client-modal »);}); document.body.appendChild(bg); } function submitNewClient(){ const name=document.getElementById(« nc-name »).value.trim(); const email=document.getElementById(« nc-email »).value.trim(); const pw=document.getElementById(« nc-pw »).value.trim(); const zone=document.getElementById(« nc-zone »).value.trim(); const credits=parseInt(document.getElementById(« nc-credits »).value)||0; if(!name||!email||!pw||!zone){toast(« Champs requis manquants », »err »);return;} if(S.users.find(u=>u.email===email)){toast(« Email dĂ©jĂ  utilisé », »err »);return;} S.users.push({id: »u »+Date.now(),role: »client »,name,email,password:pw,credits,trade:document.getElementById(« nc-trade »).value,zone,active:true,phone:document.getElementById(« nc-phone »).value.trim()}); S.logs.unshift({id:Date.now(),action:`Nouveau client : ${name}`,at:new Date().toISOString(),type: »credits »}); // Email auto au nouveau client const sub=encodeURIComponent(« Vos accĂšs Nouvel Horizon – Plateforme leads BTP »); const bod=encodeURIComponent(« Bonjour « +name+ »,\n\nVotre compte a Ă©tĂ© créé.\n\n🔐 Identifiants :\nURL : https://nouvelhorizon.online/nh-leads/\nEmail : « +email+ »\nMot de passe provisoire : « +pw+ »\n\n⚠ Changez votre mot de passe Ă  la premiĂšre connexion.\n\nL’Ă©quipe Nouvel Horizon »); window.open(« mailto: »+email+ »?subject= »+sub+ »&body= »+bod, »_blank »); toast(« Compte créé + email prĂȘt pour « +name);saveToStorage();closeModal(« add-client-modal »);renderSidebar();renderPage(); } function editClientModal(userId){ const u=S.users.find(x=>x.id===userId);if(!u)return; const bg=document.createElement(« div »);bg.className= »modal-bg »;bg.id= »edit-client-modal »; bg.innerHTML=``; bg.addEventListener(« click »,e=>{if(e.target===bg)closeModal(« edit-client-modal »);}); document.body.appendChild(bg); } function submitEditClient(userId){ const u=S.users.find(x=>x.id===userId);if(!u)return; u.name=document.getElementById(« ec-name »).value.trim()||u.name; const pw=document.getElementById(« ec-pw »).value.trim();if(pw)u.password=pw; u.trade=document.getElementById(« ec-trade »).value; u.zone=document.getElementById(« ec-zone »).value.trim()||u.zone; toast(« Profil mis Ă  jour »);saveToStorage();closeModal(« edit-client-modal »);renderSidebar();renderPage(); } function showAddLeadModal(){ const bg=document.createElement(« div »);bg.className= »modal-bg »;bg.id= »add-lead-modal »; bg.innerHTML=``; bg.addEventListener(« click »,e=>{if(e.target===bg)closeModal(« add-lead-modal »);}); document.body.appendChild(bg); } function submitLead(){ const name=document.getElementById(« fl-name »).value.trim(); const phone=document.getElementById(« fl-phone »).value.trim(); const zone=document.getElementById(« fl-zone »).value.trim(); if(!name||!phone||!zone){toast(« Nom, tĂ©lĂ©phone et zone requis », »err »);return;} S.leads.unshift({id: »l »+Date.now(),name,phone,email:document.getElementById(« fl-email »).value.trim(),zone,trade:document.getElementById(« fl-trade »).value,project:document.getElementById(« fl-project »).value.trim(),budget:document.getElementById(« fl-budget »).value.trim(),createdAt:new Date().toISOString(),assignedTo:null,status: »pending »,crmStatus: »nouveau »,notes:[]}); toast(« Contact enregistré »);saveToStorage();closeModal(« add-lead-modal »);renderSidebar();renderPage(); } function renderSupport(m){ var art=S.session||{}; var wrap=document.createElement(‘div’); wrap.style.cssText=’padding:24px;max-width:680px;margin:0 auto;’; var hdr=document.createElement(‘div’); hdr.style.cssText=’display:flex;align-items:center;gap:12px;margin-bottom:28px;’; var btnBack=document.createElement(‘button’); btnBack.textContent=’← Retour’; btnBack.style.cssText=’background:rgba(255,255,255,0.07);border:1px solid rgba(255,255,255,0.12);color:#fff;padding:8px 16px;border-radius:8px;cursor:pointer;font-size:13px;’; btnBack.onclick=function(){setPage(‘leads’);}; var h2=document.createElement(‘h2′); h2.textContent=’Support’; h2.style.cssText=’color:#fff;font-size:20px;font-weight:700;margin:0;’; hdr.appendChild(btnBack);hdr.appendChild(h2); wrap.appendChild(hdr); var card=document.createElement(‘div’); card.style.cssText=’background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.1);border-radius:16px;padding:32px;’; var iconWrap=document.createElement(‘div’); iconWrap.style.cssText=’text-align:center;margin-bottom:28px;’; iconWrap.innerHTML=’
🎧

Ouvrir un ticket support

Notre équipe vous répondra par email dans les plus brefs délais.

‘; card.appendChild(iconWrap); var lblS=document.createElement(‘label’); lblS.textContent=’Sujet *’; lblS.style.cssText=’display:block;color:rgba(255,255,255,0.7);font-size:13px;font-weight:600;margin-bottom:8px;margin-top:0;’; var sel=document.createElement(‘select’); sel.id=’tkSubject’; sel.style.cssText=’width:100%;background:rgba(30,30,50,0.95);border:1px solid rgba(255,255,255,0.12);color:#fff;padding:11px 14px;border-radius:10px;font-size:14px;outline:none;cursor:pointer;margin-bottom:16px;’; [‘– Choisir un sujet –‘,’ProblĂšme de connexion’,’Question sur mes leads’,’ProblĂšme de facturation’,’Demande de modification de compte’,’Lead incorrect ou mauvaise qualitĂ©’,’Autre’].forEach(function(t,i){ var o=document.createElement(‘option’); o.textContent=t;o.value=i===0? »:t; sel.appendChild(o); }); card.appendChild(lblS);card.appendChild(sel); var lblM=document.createElement(‘label’); lblM.textContent=’Votre message *’; lblM.style.cssText=’display:block;color:rgba(255,255,255,0.7);font-size:13px;font-weight:600;margin-bottom:8px;’; var ta=document.createElement(‘textarea’); ta.id=’tkMsg’;ta.rows=6;ta.placeholder=’DĂ©crivez votre problĂšme en dĂ©tail…’; ta.style.cssText=’width:100%;background:rgba(30,30,50,0.95);border:1px solid rgba(255,255,255,0.12);color:#fff;padding:12px 14px;border-radius:10px;font-size:14px;resize:vertical;outline:none;font-family:inherit;box-sizing:border-box;margin-bottom:16px;’; card.appendChild(lblM);card.appendChild(ta); var info=document.createElement(‘div’); info.style.cssText=’background:rgba(249,115,22,0.08);border:1px solid rgba(249,115,22,0.2);border-radius:10px;padding:12px 16px;margin-bottom:20px;display:flex;gap:10px;align-items:flex-start;’; info.innerHTML=’📧

Notre Ă©quipe vous rĂ©pondra Ă  ‘+(art.email|| »)+’

‘; card.appendChild(info); var btn=document.createElement(‘button’); btn.id=’tkBtn’; btn.textContent=’Envoyer le ticket’; btn.style.cssText=’width:100%;background:linear-gradient(135deg,#f97316,#ea580c);color:#fff;border:none;padding:14px;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;’; btn.onclick=function(ev){submitTicket(ev);}; card.appendChild(btn); wrap.appendChild(card); m.innerHTML= »; m.appendChild(wrap); } async function submitTicket(ev){ if(ev){ev.preventDefault();ev.stopPropagation();} var btn=document.getElementById(‘tkBtn’); var subj=document.getElementById(‘tkSubject’).value; var msg=document.getElementById(‘tkMsg’).value; if(!subj||!msg){toast(‘Remplissez tous les champs’,’err’);return;} btn.disabled=true;btn.textContent=’Envoi en cours…’; var art=S.session||{}; try{ var r=await fetch(‘https://formspree.io/f/mkokwgal&rsquo;,{ method:’POST’, headers:{‘Content-Type’:’application/json’,’Accept’:’application/json’}, body:JSON.stringify({email:art.email|| »,_replyto:art.email|| »,name:art.name||art.email|| »,trade:art.trade|| »,zone:art.zone|| »,subject:'[NH Support] ‘+subj,message:msg,date:new Date().toLocaleString(‘fr-FR’)}) }); if(r.ok){ toast(‘Ticket envoyĂ© ! Nous vous rĂ©pondons par email.’); setPage(‘leads’); }else{throw new Error();} }catch(e){ toast(‘Erreur envoi. Contactez support@nouvelhorizon.online’,’err’); btn.disabled=false;btn.textContent=’Envoyer le ticket’; } }