// interne.jsx — Espace interne LBC (gestion déménagement) — reproduit les écrans des captures
// Sections : Accueil · Déménagements · Calendrier · To-do & Objectifs · Clients · Opérationnel · Statistiques · Charges & Bilan · Documents · Paramètres
// Données persistées en localStorage. Réutilise Icon / euro / Avatar / KPICard / AreaChart du design system de la plateforme.
const { useState: uIS, useEffect: uIE, useReducer: uIR, useMemo: uIM } = React;

/* ---------- HELPERS ---------- */
const iNum = (x)=> parseFloat(x)||0;
const I_MONTHS = ['janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'];
const I_MONTHS_AB = ['janv.','févr.','mars','avr.','mai','juin','juil.','août','sept.','oct.','nov.','déc.'];
const I_DAYS = ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'];
const iToday = ()=> new Date().toISOString().slice(0,10);
function iFmtDate(d){ if(!d) return '—'; const [y,m,j]=d.split('-'); return parseInt(j)+' '+I_MONTHS_AB[parseInt(m)-1]+' '+y; }
function iFmtLong(d){ if(!d) return ''; const dt=new Date(d+'T00:00:00'); return I_DAYS[dt.getDay()]+' '+dt.getDate()+' '+I_MONTHS[dt.getMonth()]+' '+dt.getFullYear(); }
function iMonthKey(d){ return d?d.slice(0,7):''; }
function iMonthLabel(k){ if(!k) return ''; const [y,m]=k.split('-'); return I_MONTHS[parseInt(m)-1]+' '+y; }
function iDaysBetween(a,b){ return Math.round((new Date(b)-new Date(a))/86400000); }
function iAddMonths(dateStr, months){ if(!dateStr||!months) return ''; const d=new Date(dateStr+'T00:00:00'); d.setMonth(d.getMonth()+parseInt(months)); return d.toISOString().slice(0,10); }
function iUid(p){ return p+'-'+Date.now().toString(36)+Math.floor(Math.random()*900+100); }
function iInitials(p,n){ return (((p||'')[0]||'')+((n||'')[0]||'')||'?').toUpperCase(); }
function iK(n){ n=Math.round(n||0); const a=Math.abs(n); return a>=1000?(n/1000).toFixed(a>=10000?0:1).replace('.0','')+'k':String(n); }
function iSize(b){ b=b||0; return b>=1048576?(b/1048576).toFixed(1)+' Mo':Math.max(1,Math.round(b/1024))+' Ko'; }
function iCashSound(){ try{ const Ctx=window.AudioContext||window.webkitAudioContext; if(!Ctx) return; const ac=new Ctx(); const now=ac.currentTime;
  const ping=(freq,t0,dur)=>{ const o=ac.createOscillator(), g=ac.createGain(); o.type='square'; o.frequency.value=freq; o.connect(g); g.connect(ac.destination); g.gain.setValueAtTime(0.0001,now+t0); g.gain.exponentialRampToValueAtTime(0.22,now+t0+0.01); g.gain.exponentialRampToValueAtTime(0.0001,now+t0+dur); o.start(now+t0); o.stop(now+t0+dur+0.03); };
  ping(1318.5,0,0.13); ping(1760,0.085,0.26); setTimeout(()=>{ try{ ac.close(); }catch(e){} }, 700);
}catch(e){} }
function iCelebrate(montant){
  iCashSound();
  if(!document.getElementById('lbc-celebrate-style')){ const st=document.createElement('style'); st.id='lbc-celebrate-style'; st.textContent='@keyframes lbcpop{0%{transform:scale(.5);opacity:0}60%{transform:scale(1.06)}100%{transform:scale(1);opacity:1}}@keyframes lbcfade{to{opacity:0;transform:scale(.96)}}@keyframes lbcfall{to{transform:translateY(340px) rotate(380deg);opacity:0}}'; document.head.appendChild(st); }
  const o=document.createElement('div'); o.style.cssText='position:fixed;inset:0;z-index:99999;display:flex;align-items:center;justify-content:center;pointer-events:none;overflow:hidden;';
  o.innerHTML='<div style="background:linear-gradient(135deg,#0E2A3B,#14384E);color:#fff;padding:24px 38px;border-radius:20px;box-shadow:0 24px 70px rgba(0,0,0,.4);text-align:center;animation:lbcpop .5s cubic-bezier(.2,1.5,.4,1)"><div style="font-size:44px">🎉</div><div style="font-size:12px;opacity:.65;text-transform:uppercase;letter-spacing:.1em;margin-top:2px">Mission terminée</div><div style="font-family:Geist Mono,monospace;font-size:34px;font-weight:800;color:#7FDEAA;margin-top:6px;line-height:1">+'+euro(iNum(montant))+'</div><div style="font-size:12px;opacity:.65;margin-top:4px">enregistré au chiffre d\'affaires 💰</div></div>';
  const cols=['#B63D24','#16965A','#2563EB','#C2992E','#7FDEAA','#E0411F'];
  for(let i=0;i<34;i++){ const c=document.createElement('div'); c.style.cssText='position:absolute;top:44%;left:'+(50+(Math.random()*46-23))+'%;width:9px;height:15px;background:'+cols[i%cols.length]+';border-radius:2px;opacity:.95;transform:rotate('+(Math.random()*360)+'deg);animation:lbcfall '+(0.9+Math.random()*0.8)+'s ease-out '+(Math.random()*0.15)+'s forwards'; o.appendChild(c); }
  document.body.appendChild(o);
  setTimeout(()=>{ o.style.animation='lbcfade .3s forwards'; }, 1500);
  setTimeout(()=>{ try{ o.remove(); }catch(e){} }, 1850);
}
function iMonthRange(startMK,endMK){ const [ys,ms]=startMK.split('-').map(Number); const [ye,me]=endMK.split('-').map(Number); const a=[]; let y=ys,m=ms; let guard=0; while((y<ye||(y===ye&&m<=me))&&guard++<240){ a.push(y+'-'+String(m).padStart(2,'0')); m++; if(m>12){m=1;y++;} } return a; }
function IEvolutionChart({ rows }){
  if(!rows.length) return <IEmpty>Pas encore de données</IEmpty>;
  const n=rows.length, H=184, padT=16, padB=22, plotH=H-padT-padB;
  const volMax=Math.max(1,...rows.map(r=>r.vol));
  return (
    <div>
      <div style={{ position:'relative', height:H }}>
        {[1,.75,.5,.25,0].map((p,i)=>(<div key={i} style={{ position:'absolute', left:28, right:32, top:padT+(1-p)*plotH, borderTop:p===0?'1px solid var(--border-2)':'1px dashed var(--border)' }}>
          <span style={{ position:'absolute', left:-28, top:-7, fontSize:8.5, color:'var(--faint)', width:24, textAlign:'right' }}>{Math.round(volMax*p)}</span>
          <span style={{ position:'absolute', right:-30, top:-7, fontSize:8.5, color:'#C2992E', width:26 }}>{Math.round(p*100)}%</span>
        </div>))}
        <div style={{ position:'absolute', left:28, right:32, top:padT, height:plotH, display:'flex', alignItems:'flex-end' }}>
          {rows.map((r,i)=>(<div key={i} style={{ flex:1, display:'flex', justifyContent:'center', alignItems:'flex-end', height:'100%' }}>
            <div title={r.label+' · '+r.vol+' terminés · '+r.conv+'% conv. · '+euro(r.ca)} style={{ width:'62%', maxWidth:20, height:(r.vol/volMax*plotH)||0, background:'linear-gradient(180deg,#1d5170,var(--navy))', borderRadius:'4px 4px 0 0', minHeight:r.vol?2:0, transition:'height .5s' }} />
          </div>))}
        </div>
        <svg viewBox="0 0 100 100" preserveAspectRatio="none" style={{ position:'absolute', left:28, top:padT, height:plotH, width:'calc(100% - 60px)', overflow:'visible' }}>
          <polyline points={rows.map((r,i)=>(((i+0.5)/n*100).toFixed(2)+','+(100-r.conv).toFixed(2))).join(' ')} fill="none" stroke="#C2992E" strokeWidth="2" vectorEffect="non-scaling-stroke" />
        </svg>
        {rows.map((r,i)=>(<div key={i} title={r.conv+'% conv.'} style={{ position:'absolute', left:'calc(28px + (100% - 60px) * '+((i+0.5)/n)+')', top:padT+(1-r.conv/100)*plotH, width:7, height:7, marginLeft:-3.5, marginTop:-3.5, borderRadius:'50%', background:'#C2992E', border:'1.5px solid var(--card)' }} />))}
        <div style={{ position:'absolute', left:28, right:32, bottom:0, display:'flex' }}>
          {rows.map((r,i)=>(<div key={i} style={{ flex:1, textAlign:'center', fontSize:8.5, color:'var(--muted)', textTransform:'capitalize' }}>{r.label}</div>))}
        </div>
      </div>
      <div style={{ display:'flex', gap:18, justifyContent:'center', marginTop:10, fontSize:12 }}>
        <span style={{ display:'inline-flex', alignItems:'center', gap:6 }}><span style={{ width:9, height:9, borderRadius:2, background:'var(--navy)' }} />Déménagements terminés</span>
        <span style={{ display:'inline-flex', alignItems:'center', gap:6 }}><span style={{ width:16, height:2, background:'#C2992E' }} />Taux de conversion</span>
      </div>
    </div>
  );
}
function ICplChart({ rows, cible }){
  if(!rows.length) return <IEmpty>Pas encore de données d'acquisition payante sur la période</IEmpty>;
  const n=rows.length, H=184, padT=16, padB=22, plotH=H-padT-padB;
  const cplMax=Math.max(1, cible||0, ...rows.map(r=>r.cpl));
  return (
    <div>
      <div style={{ position:'relative', height:H }}>
        {[1,.75,.5,.25,0].map((p,i)=>(<div key={i} style={{ position:'absolute', left:36, right:10, top:padT+(1-p)*plotH, borderTop:p===0?'1px solid var(--border-2)':'1px dashed var(--border)' }}>
          <span style={{ position:'absolute', left:-36, top:-7, fontSize:8.5, color:'var(--faint)', width:32, textAlign:'right' }}>{Math.round(cplMax*p)}€</span>
        </div>))}
        {cible>0 && cible<=cplMax && <div style={{ position:'absolute', left:36, right:10, top:padT+(1-cible/cplMax)*plotH, borderTop:'2px dashed #C2362B', zIndex:2 }}>
          <span style={{ position:'absolute', right:0, top:-14, fontSize:9, color:'#C2362B', fontWeight:700, background:'var(--card)', padding:'0 4px', borderRadius:4 }}>Cible {cible}€</span>
        </div>}
        <div style={{ position:'absolute', left:36, right:10, top:padT, height:plotH, display:'flex', alignItems:'flex-end' }}>
          {rows.map((r,i)=>{ const over=cible>0&&r.cpl>cible; return (<div key={i} style={{ flex:1, display:'flex', justifyContent:'center', alignItems:'flex-end', height:'100%' }}>
            <div title={r.label+' · CPL '+(r.cpl?euro(r.cpl):'—')+' · '+r.leads+' lead(s) · '+euro(r.dep)+' dépensés'} style={{ width:'62%', maxWidth:22, height:(r.cpl/cplMax*plotH)||0, background:over?'linear-gradient(180deg,#e05a3f,#C2362B)':'linear-gradient(180deg,#3b82f6,#2563EB)', borderRadius:'4px 4px 0 0', minHeight:r.cpl?2:0, transition:'height .5s' }} />
          </div>); })}
        </div>
        <div style={{ position:'absolute', left:36, right:10, bottom:0, display:'flex' }}>
          {rows.map((r,i)=>(<div key={i} style={{ flex:1, textAlign:'center', fontSize:8.5, color:'var(--muted)', textTransform:'capitalize' }}>{r.label}</div>))}
        </div>
      </div>
      <div style={{ display:'flex', gap:18, justifyContent:'center', marginTop:10, fontSize:12 }}>
        <span style={{ display:'inline-flex', alignItems:'center', gap:6 }}><span style={{ width:9, height:9, borderRadius:2, background:'#2563EB' }} />CPL dans la cible</span>
        <span style={{ display:'inline-flex', alignItems:'center', gap:6 }}><span style={{ width:9, height:9, borderRadius:2, background:'#C2362B' }} />CPL au-dessus de la cible</span>
      </div>
    </div>
  );
}
function IFileButton({ label, onFile, small }){
  return (<label className="btn btn-ghost btn-sm" style={{ cursor:'pointer' }}>
    <Icon name="fileText" size={14} />{label||'Joindre un fichier'}
    <input type="file" style={{ display:'none' }} onChange={e=>{ const f=e.target.files&&e.target.files[0]; if(f){ const r=new FileReader(); r.onload=()=>onFile({ name:f.name, size:f.size, dataURL:f.size<2000000?r.result:null }); r.readAsDataURL(f); } e.target.value=''; }} />
  </label>);
}
function IDocChip({ doc, onRemove }){
  return (<div style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 10px', borderRadius:9, background:'var(--bg)' }}>
    <Icon name="fileText" size={15} style={{ color:'var(--navy)', flexShrink:0 }} />
    <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:12.5, fontWeight:600, color:'var(--ink)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{doc.nom}</div>
      <div style={{ fontSize:10.5, color:'var(--muted)' }}>{doc.taille}{doc.mois?' · '+iMonthLabel(doc.mois):''}</div></div>
    {doc.dataURL && <a href={doc.dataURL} download={doc.nom} style={{ color:'var(--brand)', display:'flex' }} title="Télécharger"><Icon name="download" size={15} /></a>}
    {onRemove && <button onClick={onRemove} style={{ color:'var(--muted)', display:'flex' }} title="Retirer"><Icon name="x" size={14} /></button>}
  </div>);
}

/* ---------- CONFIG ---------- */
const I_STATUS = {
  devis_a_envoyer:  { label:'Devis à envoyer',     c:'#2563EB', bg:'rgba(37,99,235,.12)',  pipe:0 },
  devis_envoye:     { label:'Devis envoyé',        c:'#C77A0A', bg:'rgba(199,122,10,.12)', pipe:1 },
  confirme:         { label:'Confirmé',            c:'#6D4AC4', bg:'rgba(109,74,196,.12)', pipe:2 },
  attente_paiement: { label:'Attente de paiement', c:'#0E8C6A', bg:'rgba(14,140,106,.12)', pipe:3 },
  termine:          { label:'Terminé',             c:'#16965A', bg:'rgba(22,150,90,.12)',  pipe:4 },
  refuse:           { label:'Refusé',              c:'#C2362B', bg:'rgba(194,54,43,.12)',  pipe:-1 },
  annule:           { label:'Annulé',              c:'#8A96A0', bg:'rgba(138,150,160,.14)',pipe:-1 },
};
const I_PIPELINE = ['devis_a_envoyer','devis_envoye','confirme','attente_paiement','termine'];
function iNextStatut(s){ const i=I_PIPELINE.indexOf(s); return (i>=0 && i<I_PIPELINE.length-1)?I_PIPELINE[i+1]:null; }
function iPrevStatut(s){ const i=I_PIPELINE.indexOf(s); return i>0?I_PIPELINE[i-1]:null; }
/* change le statut d'un déménagement ET, au passage en « devis envoyé », envoie au client
   le lien d'accès à son espace (lien magique). À utiliser depuis TOUS les boutons « Devis envoyé ». */
function iMarkDevisEnvoye(id, showToast){
  let email='', was='';
  iCommit(db=>{ const m=db.moves.find(x=>x.id===id); if(m){ was=m.statut; email=m.email; m.statut='devis_envoye'; iStampStatut(m,'devis_envoye'); } });
  if(was!=='devis_envoye' && email && window.LBC_SB && window.LBC_SB.inviteClient){
    window.LBC_SB.inviteClient(email).then(r=>{ if(showToast) showToast(r&&r.ok?('📧 Devis envoyé — lien d\'accès envoyé à '+email):'Devis envoyé (⚠️ email non parti — vérifie la config Supabase)'); });
  } else if(showToast){ showToast(email?'Devis envoyé':'Devis envoyé (pas d\'email client renseigné)'); }
}
/* ---- Relances automatiques (devis sans réponse & paiement en attente) ---- */
/* horodate les transitions de statut qui servent de point de départ aux relances */
function iStampStatut(m, s){
  if(s==='devis_envoye' && !m.devisEnvoyeAt) m.devisEnvoyeAt=iToday();
  if((s==='confirme'||s==='attente_paiement') && !m.paieFrom) m.paieFrom=iToday();
}
const I_RELANCE_DEVIS=[4,5];  // 1re relance 4j après l'envoi, 2e relance 5j après la 1re — max 2
const I_RELANCE_PAIE=[3,5];   // 1re relance 3j après confirmation, puis toutes les 5j, sans limite
/* renvoie {move,kind,n} si un devis envoyé attend une relance, sinon null */
function iRelanceDevisDue(m){
  if(m.statut!=='devis_envoye' || !m.email) return null;
  const n=iNum(m.relanceN)||0; if(n>=2) return null;
  const ref = n===0 ? (m.devisEnvoyeAt||m.createdAt) : m.relanceAt;
  if(!ref) return null;
  const need = n===0 ? I_RELANCE_DEVIS[0] : I_RELANCE_DEVIS[1];
  return iDaysBetween(ref, iToday())>=need ? { move:m, kind:'devis', n } : null;
}
/* renvoie {move,kind,n} si un paiement reste dû et attend une relance, sinon null */
function iRelancePaieDue(m){
  if(!['confirme','attente_paiement'].includes(m.statut) || !m.email) return null;
  const reste=iNum(m.montant)-iCaEncaisse(m); if(reste<=0) return null;
  const n=iNum(m.relancePaieN)||0;
  const ref = n===0 ? (m.paieFrom||m.createdAt) : m.relancePaieAt;
  if(!ref) return null;
  const need = n===0 ? I_RELANCE_PAIE[0] : I_RELANCE_PAIE[1];
  return iDaysBetween(ref, iToday())>=need ? { move:m, kind:'paie', n } : null;
}
/* envoie la relance par email (via /api/email) puis incrémente le compteur du déménagement */
function iSendRelance(move, kind, n, showToast){
  if(!move){ return; }
  const s=I_STORE.db.settings||{};
  const { subject, html } = iRelanceEmail(move, kind, n, s);
  if(showToast) showToast('📧 Envoi de la relance…');
  fetch('/api/email', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ to:move.email, subject, html, replyTo:s.email||'' }) })
    .then(r=>r.json()).then(j=>{
      if(j&&j.ok){
        iCommit(db=>{ const mm=db.moves.find(x=>x.id===move.id); if(mm){ if(kind==='devis'){ mm.relanceN=(iNum(mm.relanceN)||0)+1; mm.relanceAt=iToday(); } else { mm.relancePaieN=(iNum(mm.relancePaieN)||0)+1; mm.relancePaieAt=iToday(); } } });
        if(showToast) showToast('✅ Relance envoyée à '+move.email);
      } else if(showToast){ showToast('⚠️ Échec de l\'envoi'+(j&&j.error?' ('+j.error+')':'')); }
    }).catch(()=>{ if(showToast) showToast('⚠️ Erreur réseau — relance non envoyée'); });
}
function iMailWrap(s, inner){
  const ent=iEsc(s.entreprise||'LBC Déménagement'), tel=iEsc(s.tel||''), mail=iEsc(s.email||'');
  return '<div style="font-family:Arial,Helvetica,sans-serif;background:#f4f1ea;padding:24px 0">'
    +'<div style="max-width:520px;margin:0 auto;background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,.06)">'
    +'<div style="background:#14384E;padding:20px 28px"><div style="color:#F5EFE3;font-size:20px;font-weight:800;letter-spacing:.5px">'+ent+'</div></div>'
    +'<div style="padding:26px 28px;color:#1f2a30;font-size:15px;line-height:1.6">'+inner+'</div>'
    +'<div style="padding:16px 28px;background:#f7f5f0;border-top:1px solid #e7e2d6;color:#6b7780;font-size:12.5px;line-height:1.6">'+ent+(tel?' · '+tel:'')+(mail?' · '+mail:'')+'</div>'
    +'</div></div>';
}
/* construit {subject, html} d'une relance selon le type (devis/paie) et le rang n (0,1,2…) */
function iRelanceEmail(m, kind, n, s){
  const prenom=iEsc(m.prenom||''), owner=iEsc(s.ownerName||('L\'équipe '+(s.entreprise||'LBC'))), tel=iEsc(s.tel||'');
  const formule=iEsc((I_FORMULES[m.formule]||{}).label||'');
  const trajet=[m.dep&&m.dep.ville, m.arr&&m.arr.ville].filter(Boolean).map(iEsc).join(' → ');
  const tj=trajet?(' ('+trajet+')'):'';
  if(kind==='devis'){
    if(n===0){
      const inner='<p>Bonjour '+prenom+',</p>'
        +'<p>Vous avez récemment demandé un devis pour votre déménagement'+tj+', et nous vous en remercions.</p>'
        +'<p>Nous voulions nous assurer que vous l\'avez bien reçu. Une question sur la formule <b>'+formule+'</b>, ou besoin de l\'ajuster ? Nous sommes là pour ça.</p>'
        +'<p>Pour confirmer ou en discuter, répondez simplement à cet email'+(tel?' ou appelez-nous au <b>'+tel+'</b>':'')+'.</p>'
        +'<p>À très vite,<br/>'+owner+'</p>';
      return { subject:'Votre devis déménagement vous attend 📦', html:iMailWrap(s, inner) };
    }
    const inner='<p>Bonjour '+prenom+',</p>'
      +'<p>Nous revenons vers vous une dernière fois au sujet de votre devis de déménagement'+tj+'.</p>'
      +'<p>Les disponibilités à vos dates partent vite. Si vous souhaitez réserver votre créneau, c\'est le bon moment de nous le faire savoir.</p>'
      +'<p>Un simple mot en réponse'+(tel?' ou un appel au <b>'+tel+'</b>':'')+' suffit, et on s\'occupe du reste.</p>'
      +'<p>Bien à vous,<br/>'+owner+'</p>';
    return { subject:'Dernière relance — votre devis déménagement', html:iMailWrap(s, inner) };
  }
  const reste=euro(iNum(m.montant)-iCaEncaisse(m)), iban=iEsc(s.iban||'');
  const virement=iban?('<p>Par virement : <b>'+iban+'</b> (indiquez votre nom en référence).</p>'):'';
  if(n===0){
    const inner='<p>Bonjour '+prenom+',</p>'
      +'<p>Merci d\'avoir confirmé votre déménagement avec nous'+tj+' 🙌</p>'
      +'<p>Pour bloquer définitivement votre date, il reste un règlement de <b style="color:#14384E">'+reste+'</b> à effectuer.</p>'
      +virement
      +'<p>Dès réception, tout est calé. Une question ? Répondez à cet email'+(tel?' ou appelez le <b>'+tel+'</b>':'')+'.</p>'
      +'<p>Merci,<br/>'+owner+'</p>';
    return { subject:'Finalisons votre déménagement — règlement en attente', html:iMailWrap(s, inner) };
  }
  if(n===1){
    const inner='<p>Bonjour '+prenom+',</p>'
      +'<p>Petit rappel : le règlement de <b style="color:#C77A0A">'+reste+'</b> pour votre déménagement'+tj+' est toujours en attente.</p>'
      +'<p>Sans ce règlement, nous ne pouvons malheureusement pas garantir votre créneau, qui pourrait être proposé à un autre client.</p>'
      +virement
      +'<p>Merci de régulariser rapidement'+(tel?', ou appelez-nous au <b>'+tel+'</b> si besoin':'')+'.</p>'
      +'<p>Cordialement,<br/>'+owner+'</p>';
    return { subject:'Rappel : règlement en attente pour votre déménagement', html:iMailWrap(s, inner) };
  }
  const inner='<p>Bonjour '+prenom+',</p>'
    +'<p>Malgré nos précédents messages, le règlement de <b style="color:#C2362B">'+reste+'</b> reste impayé pour votre déménagement'+tj+'.</p>'
    +'<p><b>Sans règlement sous 48h, votre réservation sera annulée</b> et le créneau libéré.</p>'
    +(iban?('<p>Virement immédiat : <b>'+iban+'</b> (votre nom en référence).</p>'):'')
    +'<p>Si un imprévu vous empêche de payer, contactez-nous'+(tel?' au <b>'+tel+'</b>':'')+' pour trouver une solution.</p>'
    +'<p>'+owner+'</p>';
  return { subject:'Important : votre paiement reste en attente', html:iMailWrap(s, inner) };
}
const I_FORMULES = {
  eco:     { label:'Coup de main',          desc:"L'essentiel : transport + chargement/déchargement par l'équipe", c:'#14384E' },
  standard:{ label:'Mains libres',          desc:'Le confort : protection du mobilier + démontage/remontage + emballage des fragiles', c:'#2563EB' },
  premium: { label:'Mains dans les poches', desc:'Clé en main : emballage de tous les cartons + déballage & installation à l’arrivée', c:'#B63D24' },
};
const I_INC_STATUTS = { declare:'Déclaré', en_cours:'En cours', amiable:'Réglé à l\'amiable', assurance:'Réglé avec assurance' };
const I_INC_COLORS = { declare:'#C2362B', en_cours:'#C77A0A', amiable:'#16965A', assurance:'#2563EB' };
function iIncidentOuvert(i){ return i && i.statut!=='amiable' && i.statut!=='assurance'; }
const I_SOURCES = {
  site_web:'🌐 Site web', google:'🔍 Google', meta:'📸 Insta / Facebook', leboncoin:'🟠 Leboncoin',
  bouche_a_oreille:'🗣 Bouche à oreille', partenaire:'🤝 Partenaire', client_fidele:'⭐ Client fidèle', autre:'💡 Autre',
};
const I_QUADRANTS = {
  now:    { key:'now',    title:'Faire maintenant', sub:'Urgent & Important',     c:'#B63D24', emoji:'⚡' },
  plan:   { key:'plan',   title:'Planifier',        sub:'Important, pas urgent',  c:'#2563EB', emoji:'🗓️' },
  deleg:  { key:'deleg',  title:'Déléguer',         sub:'Urgent, moins important',c:'#C77A0A', emoji:'🤝' },
  later:  { key:'later',  title:'Plus tard',        sub:'Ni urgent, ni important',c:'#8A96A0', emoji:'🗑️' },
};
const I_CHARGE_CATS = ['Loyer / Stockage','Assurance','Carburant','Entretien véhicules','Salaires','Comptabilité','Publicité / Marketing','Matériel & fournitures','Téléphone / Internet','Frais bancaires','Formation','Autre'];
const I_CAT_C = { 'Loyer / Stockage':'#FFA726','Assurance':'#AB47BC','Carburant':'#FF7043','Entretien véhicules':'#8D6E63','Salaires':'#42A5F5','Comptabilité':'#5C6BC0','Publicité / Marketing':'#EF5350','Matériel & fournitures':'#26C6DA','Téléphone / Internet':'#66BB6A','Frais bancaires':'#26A69A','Formation':'#EC407A','Autre':'#BDBDBD' };

const I_ASC_TAILLES = ['Aucun','1 personne','2 personnes','3-4 personnes','6+ personnes','Monte-charge'];
function iNewSide(){ return { rue:'', ville:'', cp:'', logement:'appartement', etage:'0', ascenseur:false, ascTaille:'Aucun', portage:'', parking:false, notes:'' }; }
function iNewMove(){ return {
  id:iUid('DEM'), num:'', createdAt:iToday(), prenom:'', nom:'', tel:'', email:'', source:'site_web',
  formule:'standard', statut:'devis_a_envoyer', volume:'', cartons:'', notes:'',
  formulaireType:'', inventaire:[], options:{ demontage:false, emballage:false, gardeMeuble:false },
  flexibilite:'', contactPref:'', fragiles:[], demonter:[],
  rdvDate:'', rdvHeure:'', date:'', heure:'08:00',
  km:'', allerRetour:false, peage:'', peageAR:false, fraisMateriel:'', camions:[], equipe:[], mainOeuvre:[], fraisAnnexes:[], kmApplique:false,
  montant:'', cout:'', acompte:'', acompteRecu:false, soldeRecu:false,
  devisEnvoyeAt:'', paieFrom:'', relanceN:0, relanceAt:'', relancePaieN:0, relancePaieAt:'',
  dep:iNewSide(), arr:iNewSide(),
}; }
/* ---- Calcul des coûts d'un déménagement ---- */
function iFactor(m){ return m.allerRetour?2:1; }
/* conso d'un camion affecté (L/100km) : flotte = sa fiche ; loué = saisie ou défaut */
function iCamionConso(c){ const def=iNum(I_STORE.db.settings&&I_STORE.db.settings.consoDefaut)||25;
  if(c.loue) return iNum(c.conso)||def;
  return iNum((I_STORE.db.camions.find(x=>x.id===c.id)||{}).conso)||def; }
/* essence = prix au litre × conso × km × facteur (somme sur chaque camion affecté) */
function iEssence(m){
  const prixL=iNum(I_STORE.db.settings&&I_STORE.db.settings.prixLitre)||1.8;
  const def=iNum(I_STORE.db.settings&&I_STORE.db.settings.consoDefaut)||25;
  const km=iNum(m.km), factor=iFactor(m); if(!km) return 0;
  const camions=(m.camions||[]);
  if(!camions.length) return Math.round(km*(def/100)*prixL*factor);
  return Math.round(camions.reduce((a,c)=>a + km*(iCamionConso(c)/100)*prixL*factor, 0));
}
function iLocationCamions(m){ return (m.camions||[]).filter(c=>c.loue).reduce((a,c)=>a+iNum(c.location),0); }
function iFraisAnnexes(m){ return (m.fraisAnnexes||[]).reduce((a,x)=>a+iNum(x.montant),0); }
/* main d'œuvre = somme du coût de chaque personne (équipe + externes) */
function iManutention(m){ return (m.mainOeuvre||[]).reduce((a,p)=>a+iNum(p.cout),0); }
function iCoutTotal(m){
  // le ×2 essence vient de allerRetour ; le péage a son propre ×2 (peageAR) car parfois péage à l'aller seulement
  const detail = iEssence(m) + iNum(m.peage)*(m.peageAR?2:1) + iLocationCamions(m) + iNum(m.fraisMateriel) + iFraisAnnexes(m) + iManutention(m);
  // rétro-compat : si aucun détail saisi mais un ancien coût manuel existe, on le garde
  if(detail===0 && !iNum(m.km) && !(m.camions||[]).length && !(m.fraisAnnexes||[]).length && !(m.mainOeuvre||[]).length && iNum(m.cout)) return iNum(m.cout);
  return detail;
}
/* péage estimé : autoroute payante surtout en longue distance (gratuit en local) */
/* péage estimé : gratuit en local (<60 km) ; au-delà, on facture la portion autoroute (km - 30 urbains) */
function iPeageEstime(km){ const s=I_STORE.db.settings||{}; const k=iNum(km); const rate=iNum(s.peageKm)||0.18; return k>60?Math.round((k-30)*rate):0; }
/* multiplicateur selon la formule (vente) */
function iFormMult(f){ return f==='premium'?1.45:f==='standard'?1.2:1.0; }
/* surcoût d'accès difficile (étage sans ascenseur, portage long) — main d'œuvre supplémentaire */
function iAccesSurcharge(m){
  let s=0;
  ['dep','arr'].forEach(k=>{ const side=m[k]||{}; const et=iNum(side.etage);
    if(!side.ascenseur) s+=et*20; else if((side.ascTaille||'Aucun')==='1 personne') s+=et*8;
    const port=iNum(side.portage); if(port>20) s+=(port-20)*3; });
  return s;
}
/* prix conseillé à proposer au client = volume×tarif m³ + distance×tarif km, ajusté par formule,
   + surcoût d'accès, avec un plancher = coût réel × 1,6 (jamais vendre à perte) */
function iPrixConseille(m){
  const s=I_STORE.db.settings||{};
  const vol=iNum(m.volume), km=iNum(m.km);
  const base=vol*(iNum(s.prixM3)||40) + km*(iNum(s.prixKmVente)||1.2);
  const p=Math.round((base*iFormMult(m.formule) + iAccesSurcharge(m))/10)*10;
  const plancher=Math.round(iCoutTotal(m)*1.6/10)*10;
  return Math.max(p, plancher);
}
/* Volumes de transport réalistes (m³) par meuble courant. Sert à estimer le volume total
   et à éviter de sous-estimer (donc sous-facturer). Recherche partielle sur le libellé. */
const I_MEUBLES_VOL = {
  'canapé 2 places':1.5,'canapé 3 places':2.0,'canapé d\'angle':3.0,'canapé convertible':2.2,'canapé':1.8,'fauteuil':0.8,'pouf':0.3,
  'table basse':0.5,'meuble tv':0.8,'bibliothèque':1.2,'buffet':1.3,'vaisselier':1.4,'étagère':0.7,'meuble de rangement':1.0,
  'lit simple':1.0,'lit double':1.8,'lit 2 places':1.8,'lit king size':2.2,'lit électrique':2.0,'lit coffre':2.0,'lit superposé':2.0,'lit 1 place':1.0,'sommier':1.0,'matelas double':1.0,'matelas':0.8,'lit':1.5,
  'armoire 2 portes':1.6,'armoire 3 portes':2.3,'armoire':1.8,'dressing':2.0,'penderie':1.5,'commode':0.7,'table de chevet':0.2,'coiffeuse':0.6,'miroir':0.3,
  'table à manger':1.0,'table + chaises':1.6,'table de salle à manger':1.2,'table':0.8,'chaise':0.2,'tabouret':0.15,'banc':0.5,
  'bureau':0.9,'chaise de bureau':0.4,'caisson':0.3,
  'réfrigérateur':0.7,'frigo américain':1.1,'frigo':0.7,'congélateur':0.7,'lave-linge':0.6,'machine à laver':0.6,'sèche-linge':0.6,'lave-vaisselle':0.5,'four':0.3,'cuisinière':0.5,'micro-ondes':0.1,'meuble de cuisine':0.8,
  'télévision':0.3,'écran':0.2,'ordinateur':0.1,'tv':0.3,
  'vélo':0.6,'tondeuse':0.4,'établi':1.0,'barbecue':0.5,'salon de jardin':2.0,'parasol':0.3,
  'piano droit':2.5,'piano à queue':4.0,'piano':2.5,'coffre-fort':0.8,'aquarium':0.5,'machine de sport':1.2,'tapis de course':1.0,'billard':2.5,'baby-foot':1.2,'table de ping-pong':1.5,'jacuzzi':3.0,'spa':3.0,'cave à vin':0.8,'borne d\'arcade':1.0,'statue':0.6,
  'carton standard':0.05,'carton livres':0.04,'carton':0.05,
};
function iMeubleVol(label){ if(!label) return 0; const s=String(label).toLowerCase().trim();
  if(I_MEUBLES_VOL[s]!=null) return I_MEUBLES_VOL[s];
  let best=0; for(const k in I_MEUBLES_VOL){ if(s.includes(k) && I_MEUBLES_VOL[k]>best) best=I_MEUBLES_VOL[k]; }
  return best || 0.5; }
/* volume d'une ligne d'inventaire : valeur saisie sinon estimée d'après le meuble */
function iInvVol(it){ const v=it&&it.volume; return (v!=null && v!=='') ? iNum(v) : iMeubleVol(it&&it.meuble); }
function iInvTotal(m){ return Math.round((m.inventaire||[]).reduce((a,it)=>a+(iInvVol(it)*(iNum(it.quantite)||1)),0)); }
/* Poids réalistes (kg) par meuble — pour éviter la surcharge (charge utile du camion) */
const I_MEUBLES_KG = {
  'canapé 2 places':45,'canapé 3 places':60,'canapé d\'angle':95,'canapé convertible':75,'canapé':55,'fauteuil':25,'pouf':6,
  'table basse':15,'meuble tv':25,'bibliothèque':45,'buffet':55,'vaisselier':60,'étagère':20,
  'lit simple':35,'lit double':55,'lit 2 places':55,'lit king size':75,'lit électrique':90,'lit coffre':75,'matelas double':28,'matelas':20,'sommier':20,'lit':50,
  'armoire 2 portes':60,'armoire 3 portes':95,'armoire':70,'dressing':70,'commode':40,'table de chevet':8,'coiffeuse':30,'miroir':12,
  'table à manger':40,'table':30,'chaise':5,'tabouret':3,'banc':15,
  'bureau':35,'chaise de bureau':12,'caisson':18,
  'réfrigérateur':65,'frigo américain':110,'frigo':65,'congélateur':55,'lave-linge':70,'machine à laver':70,'sèche-linge':35,'lave-vaisselle':45,'four':35,'cuisinière':50,'micro-ondes':12,'meuble de cuisine':35,
  'télévision':15,'écran':8,'ordinateur':6,
  'vélo':14,'tondeuse':25,'établi':45,'barbecue':25,'salon de jardin':50,'parasol':10,
  'piano droit':220,'piano à queue':350,'piano':220,'coffre-fort':120,'aquarium':30,'billard':280,'baby-foot':75,'table de ping-pong':50,'jacuzzi':250,'spa':250,'machine de sport':80,'statue':40,
  'carton livres':25,'carton standard':15,'carton':18,
};
function iMeubleKg(label){ if(!label) return 0; const s=String(label).toLowerCase().trim();
  if(I_MEUBLES_KG[s]!=null) return I_MEUBLES_KG[s];
  let best=0; for(const k in I_MEUBLES_KG){ if(s.includes(k) && I_MEUBLES_KG[k]>best) best=I_MEUBLES_KG[k]; }
  return best || Math.round(iMeubleVol(label)*80); }
/* poids total à charger : depuis l'inventaire si détaillé, sinon estimé ~110 kg/m³ */
function iPoidsTotal(m){ const fromInv=(m.inventaire||[]).reduce((a,it)=>a+(iMeubleKg(it.meuble)*(iNum(it.quantite)||1)),0); return fromInv>0 ? Math.round(fromInv) : Math.round(iNum(m.volume)*110); }
/* charge utile (kg) d'un camion affecté : saisie pour un loué, fiche flotte sinon */
function iChargeUtile(c){ if(!c) return 0; if(c.loue) return iNum(c.chargeUtile); const fl=I_STORE.db.camions.find(x=>x.id===c.id)||{}; return iNum(fl.chargeUtile); }
/* Listes pré-définies issues du formulaire du site (à ne pas réinventer) */
const I_SITE_FRAGILES = ["Vaisselle & verrerie","Miroirs & vitres","Œuvres d'art / tableaux","TV & écrans","Informatique","Instruments de musique","Luminaires","Cave à vin"];
const I_SITE_DEMONTAGE = ["Lit","Armoire / dressing","Table","Meuble en kit","Cuisine équipée","Étagères","Bureau"];
const I_SITE_MEUBLES = ["Canapé","Canapé d'angle","Fauteuil","Table basse","Meuble TV","Télévision","Bibliothèque","Lit 2 places","Lit king size","Lit électrique","Lit coffre","Lit 1 place / enfant","Matelas","Armoire","Dressing","Commode","Table de chevet","Miroir","Réfrigérateur","Frigo américain","Congélateur","Lave-vaisselle","Cuisinière / four","Micro-ondes","Table à manger","Chaise","Buffet / vaisselier","Bureau","Chaise de bureau","Écran / ordinateur","Étagère","Caisson de rangement","Lave-linge","Sèche-linge","Salon de jardin","Table extérieure","Barbecue","Parasol","Vélo","Établi / outils","Piano droit","Piano à queue","Coffre-fort","Billard","Baby-foot","Table de ping-pong","Jacuzzi / spa","Aquarium","Cave à vin","Cartons"];
/* km parcourus par un camion = km initial + somme des déménagements terminés qui l'utilisent (×facteur), hors location */
function iCamionKm(camionId){ const base=iNum((I_STORE.db.camions.find(c=>c.id===camionId)||{}).kmInitial);
  const add=I_STORE.db.moves.filter(m=>m.statut==='termine' && (m.camions||[]).some(c=>c.id===camionId && !c.loue)).reduce((a,m)=>a+iNum(m.km)*iFactor(m),0);
  return base+add; }
function iEmployeMoves(empId){ return I_STORE.db.moves.filter(m=>m.statut==='termine' && ((m.mainOeuvre||[]).some(p=>p.id===empId) || (m.equipe||[]).includes(empId))).length; }

/* ---------- FLOTTE : base modèles + entretiens ---------- */
const I_CAMION_MARQUES = {
  'Renault':    ['Master','Trafic','Kangoo','Maxity','Midlum','D Wide','D'],
  'Mercedes':   ['Sprinter','Vito','Citan','Atego','Vario'],
  'Iveco':      ['Daily','Eurocargo','Stralis'],
  'Fiat':       ['Ducato','Talento','Doblo','Scudo'],
  'Peugeot':    ['Boxer','Expert','Partner'],
  'Citroën':    ['Jumper','Jumpy','Berlingo'],
  'Ford':       ['Transit','Transit Custom','Transit Connect'],
  'Volkswagen': ['Crafter','Transporter','Caddy'],
  'Opel':       ['Movano','Vivaro','Combo'],
  'Nissan':     ['Interstar','NV400','NV300','Primastar'],
  'MAN':        ['TGE','TGL','TGM'],
  'Toyota':     ['Proace'],
  'Autre':      [],
};
const I_MOTORISATIONS = ['Diesel 2.0','Diesel 2.3','Diesel 2.5','Diesel 3.0','Essence','Électrique','GPL','Hybride'];
const I_TAILLES_CAMION = ['< 12 m³ (fourgon)','12 m³','14 m³','16 m³','20 m³','22 m³','> 22 m³ (porteur)'];
/* recommandations constructeur génériques (modifiables par l'utilisateur) — mois / km */
const I_ENTRETIENS_DEFAUT = [
  { type:'Contrôle technique',              moisInterval:12, kmInterval:0 },
  { type:'Vidange + filtre à huile',        moisInterval:12, kmInterval:20000 },
  { type:'Filtre à air',                    moisInterval:24, kmInterval:40000 },
  { type:'Filtre habitacle',                moisInterval:12, kmInterval:20000 },
  { type:'Filtre à gazole',                 moisInterval:24, kmInterval:40000 },
  { type:'Plaquettes de frein',             moisInterval:0,  kmInterval:40000 },
  { type:'Disques de frein',                moisInterval:0,  kmInterval:80000 },
  { type:'Pneumatiques',                    moisInterval:0,  kmInterval:50000 },
  { type:'Courroie / chaîne de distribution',moisInterval:60, kmInterval:150000 },
  { type:'Liquide de frein',                moisInterval:24, kmInterval:0 },
  { type:'Géométrie / parallélisme',        moisInterval:24, kmInterval:40000 },
];
function iDefautEntretiens(){ return I_ENTRETIENS_DEFAUT.map(e=>({ ...e, dernierDate:'', dernierKm:'', prochainDate:'' })); }
/* échéance d'un entretien : date (override manuel ou dernier+intervalle) + km (dernier+intervalle) */
function iEntretienEcheance(e, kmNow){
  const dueDate = e.prochainDate || (e.dernierDate && e.moisInterval ? iAddMonths(e.dernierDate, e.moisInterval) : '');
  const dueKm = (e.dernierKm!=='' && e.dernierKm!=null && iNum(e.kmInterval)) ? iNum(e.dernierKm)+iNum(e.kmInterval) : null;
  return { dueDate, dueKm, kmRestant: dueKm!=null ? dueKm-kmNow : null };
}
/* entretiens dus/à venir pour un camion. seuilJ = jours d'anticipation, seuilKm = km d'anticipation */
function iCamionEntretiensDus(c, seuilJ, seuilKm){
  seuilJ=seuilJ==null?30:seuilJ; seuilKm=seuilKm==null?2000:seuilKm;
  const today=iToday(), kmNow=iCamionKm(c.id), out=[];
  (c.entretiens||[]).forEach(e=>{
    const { dueDate, dueKm, kmRestant } = iEntretienEcheance(e, kmNow);
    let statut=null; const raisons=[];
    if(dueDate){ const d=iDaysBetween(today,dueDate); if(d<0){ statut='retard'; raisons.push('en retard de '+(-d)+ 'j'); } else if(d<=seuilJ){ statut=statut||'bientot'; raisons.push('dans '+d+'j'); } }
    if(kmRestant!=null){ if(kmRestant<0){ statut='retard'; raisons.push((-kmRestant).toLocaleString('fr-FR')+' km dépassés'); } else if(kmRestant<=seuilKm){ statut=statut||'bientot'; raisons.push('dans '+kmRestant.toLocaleString('fr-FR')+' km'); } }
    if(statut) out.push({ camId:c.id, camNom:c.nom, type:e.type, statut, dueDate, label:raisons.join(' · ') });
  });
  return out;
}
function iToutesAlertesFlotte(){ return (I_STORE.db.camions||[]).flatMap(c=>iCamionEntretiensDus(c)); }
function iStockBas(){ return (I_STORE.db.materiel||[]).filter(m=>iNum(m.unites)<=iNum(m.seuil)); }
/* ---------- STOCKAGE : facturation mensuelle automatique ---------- */
function iBoxBillingDay(box){ if(!box||!box.startDate) return 1; return new Date(box.startDate+'T00:00:00').getDate(); }
function iBoxNextBill(box){ if(!box||!box.startDate) return ''; const start=new Date(box.startDate+'T00:00:00'); const day=start.getDate(); const today=new Date(iToday()+'T00:00:00');
  if(start.getTime()>today.getTime()) return box.startDate;
  let d=new Date(today.getFullYear(), today.getMonth(), day); if(d.getTime()<today.getTime()) d=new Date(today.getFullYear(), today.getMonth()+1, day);
  return d.toISOString().slice(0,10); }
function iBoxEcheances(box){ if(!box||!box.startDate) return []; const start=new Date(box.startDate+'T00:00:00'); const day=start.getDate(); const today=new Date(iToday()+'T00:00:00'); const n=box.libre?null:iNum(box.months); const out=[];
  for(let i=0;i<240;i++){ if(n!=null && i>=n) break; const d=new Date(start.getFullYear(), start.getMonth()+i, day);
    if(n==null && d.getTime()>new Date(today.getFullYear(), today.getMonth()+1, day).getTime()) break;
    out.push({ date:d.toISOString().slice(0,10), montant:iNum(box.price), du:d.getTime()<=today.getTime() }); }
  return out; }
function iBoxFactureCumul(box){ return iBoxEcheances(box).filter(e=>e.du).reduce((a,e)=>a+e.montant,0); }
function iNewTask(quad){ return { id:iUid('T'), title:'', quad:quad||'plan', done:false, due:'', auto:false, subtasks:[], kind:'task' }; }
function iNewEvent(){ return { id:iUid('EV'), kind:'event', title:'', due:'', heure:'', lieu:'', quad:'event', done:false, auto:false, subtasks:[] }; }
function iSampleWebLead(){
  const noms=[['Lucie','Martin'],['Karim','Benali'],['Sophie','Garnier'],['Thomas','Roux'],['Inès','Faure'],['Hugo','Lambert']];
  const villes=[['Nice','06000'],['Cannes','06400'],['Antibes','06600'],['Monaco','98000'],['Cagnes-sur-Mer','06800'],['Menton','06500']];
  const dests=[['Marseille','13001'],['Lyon','69002'],['Paris','75011'],['Bordeaux','33000'],['Toulouse','31000'],['Montpellier','34000']];
  const pick=a=>a[Math.floor(Math.random()*a.length)];
  const r2=()=>Math.floor(10+Math.random()*89);
  const [pre,nom]=pick(noms); const [vd,cpd]=pick(villes); const [va,cpa]=pick(dests);
  const detaille=Math.random()<0.6;
  const base={ source:'site_web', formulaireType:detaille?'detaille':'basique', formule:detaille?pick(['standard','premium']):'',
    client:{ prenom:pre, nom, tel:'06 '+r2()+' '+r2()+' '+r2()+' '+r2(), email:(pre+'.'+nom).toLowerCase()+'@email.fr' },
    depart:(()=>{ const asc=Math.random()<0.5; return { adresse:detaille?(Math.floor(1+Math.random()*40)+' rue de la Buffa'):'', ville:vd, cp:cpd, etage:Math.floor(Math.random()*5), ascenseur:asc, ascTaille:asc?pick(['1 personne','2 personnes','3-4 personnes']):'Aucun', portage:Math.floor(Math.random()*40), logement:'appartement' }; })(),
    arrivee:(()=>{ const asc=Math.random()<0.5; return { adresse:detaille?(Math.floor(1+Math.random()*40)+' avenue des Pins'):'', ville:va, cp:cpa, etage:Math.floor(Math.random()*3), ascenseur:asc, ascTaille:asc?pick(['1 personne','2 personnes','3-4 personnes']):'Aucun', portage:Math.floor(Math.random()*40), logement:Math.random()<0.5?'maison':'appartement' }; })(),
    dateSouhaitee:'2026-'+String(7+Math.floor(Math.random()*3)).padStart(2,'0')+'-'+String(1+Math.floor(Math.random()*27)).padStart(2,'0'),
    flexibilite:pick(['Date ferme','± quelques jours','Dans le mois','Pas encore décidé']),
    contactPref:pick(['Téléphone','Email','SMS']),
    message:detaille?'Stationnement à réserver devant le départ.':'' };
  if(detaille){
    base.fragiles=[{label:'Vaisselle & verrerie',qty:2},{label:'Miroirs & vitres',qty:1}];
    base.demonter=[{label:'Lit',qty:1},{label:'Armoire / dressing',qty:1}];
    base.inventaire=[ {piece:'Salon',meuble:'Canapé 3 places',quantite:1,volume:2.5},{piece:'Salon',meuble:'Meuble TV',quantite:1,volume:1.2},{piece:'Chambre',meuble:'Lit double',quantite:1,volume:1.8},{piece:'Chambre',meuble:'Armoire 3 portes',quantite:1,volume:2.2},{piece:'Cuisine',meuble:'Table + 4 chaises',quantite:1,volume:1.5} ];
    base.volumeEstime=Math.round(base.inventaire.reduce((a,x)=>a+x.volume*x.quantite,0))+8;
    base.cartons=20+Math.floor(Math.random()*30);
    base.options={ demontage:true, emballage:Math.random()<0.5, gardeMeuble:false };
  }
  return base;
}

/* ---------- ADAPTATEURS : base unique partagée (réseau partenaire ↔ déménagement) ---------- */
const PS2STATUT = { recu:'devis_a_envoyer', devis:'devis_envoye', accepte:'confirme', effectue:'termine', verse:'termine', perdu:'refuse' };
function statutToPartner(s){ return { devis_a_envoyer:'recu', devis_envoye:'devis', confirme:'accepte', attente_paiement:'accepte', termine:'effectue', refuse:'perdu', annule:'perdu' }[s]||'recu'; }
const I_FR_MO = { 'janv.':1,'févr.':2,'fevr.':2,'mars':3,'avr.':4,'mai':5,'juin':6,'juil.':7,'août':8,'aout':8,'sept.':9,'oct.':10,'nov.':11,'déc.':12,'dec.':12 };
function frToISO(str){ if(!str) return ''; const m=String(str).match(/(\d{1,2})\s+([A-Za-zàâäéèêëîïôûùç.]+)\s+(\d{4})/); if(!m) return ''; const mo=I_FR_MO[m[2].toLowerCase()]; if(!mo) return ''; return m[3]+'-'+String(mo).padStart(2,'0')+'-'+String(parseInt(m[1])).padStart(2,'0'); }
function partnerRate(partnerId){ const A=window.LBC_ADMIN; if(A&&partnerId){ const p=A.PARTNERS.find(x=>x.id===partnerId); if(p) return A.RATE[p.tier]||6; } return (window.LBC_DATA&&window.LBC_DATA.PARTNER.rate)||6; }
function rawLeadToMove(r){ return {
  id:r.id, num:r.id.replace(/\D/g,''), createdAt:frToISO(r.dates&&r.dates.recu)||iToday(),
  prenom:r.first, nom:r.last, tel:r.phone||'', email:r.email||'', source:'partenaire',
  partenaire:'Agence Riviera Immobilier', partnerId:'P-01',
  formule:r.formule==='complete'?'premium':'standard', statut:PS2STATUT[r.status]||'devis_a_envoyer',
  volume:'', notes:r.notes||'', rdvDate:'', rdvHeure:'',
  date:frToISO(r.move)||'', heure:'08:00',
  montant:String(r.value||''), cout:r.value?String(Math.round(r.value*0.45)):'',
  acompte:r.value?String(Math.round(r.value*0.3)):'',
  acompteRecu:['accepte','effectue','verse'].includes(r.status), soldeRecu:['effectue','verse'].includes(r.status),
  commissionVerse:r.status==='verse', avis:r.status==='verse'?5:undefined,
  dep:{ ...iNewSide(), ville:r.from }, arr:{ ...iNewSide(), ville:r.to },
  _lead:{ dates:r.dates, submitted:r.submitted, move:r.move },
}; }
function netLeadToMove(l){ const A=window.LBC_ADMIN; const p=A?A.PARTNERS.find(x=>x.company===l.partner):null; const parts=(l.client||'').split(' '); return {
  id:l.id, num:l.id.replace(/\D/g,''), createdAt:frToISO(l.date)||iToday(),
  prenom:parts[0]||l.client||'', nom:parts.slice(1).join(' '), tel:'', email:'', source:'partenaire',
  partenaire:l.partner, partnerId:p?p.id:'',
  formule:'standard', statut:PS2STATUT[l.status]||'devis_a_envoyer',
  volume:'', notes:'', rdvDate:'', rdvHeure:'',
  date:frToISO(l.date)||'', heure:'08:00',
  montant:String(l.value||''), cout:l.value?String(Math.round(l.value*0.45)):'',
  acompte:l.value?String(Math.round(l.value*0.3)):'',
  acompteRecu:['accepte','effectue','verse'].includes(l.status), soldeRecu:['effectue','verse'].includes(l.status),
  commissionVerse:l.status==='verse',
  dep:{ ...iNewSide(), ville:l.from }, arr:{ ...iNewSide(), ville:l.to },
}; }
// Vue partenaire d'un déménagement (pour l'espace partenaire) — statut/commission recalculés en direct
function dealToLead(m){
  const ps=statutToPartner(m.statut); const value=iNum(m.montant); const rate=partnerRate(m.partnerId);
  const dates=(m._lead&&m._lead.dates)||{ recu:iFmtDate(m.createdAt), contact:'', devis:m.statut!=='devis_a_envoyer'?iFmtDate(m.createdAt):'', accepte:['confirme','attente_paiement','termine'].includes(m.statut)?iFmtDate(m.createdAt):'', move:iFmtDate(m.date), toPay:'', paid:'' };
  return { id:m.id, first:m.prenom, last:m.nom, phone:m.tel, email:m.email,
    from:m.dep.ville, to:m.arr.ville, formule:m.formule==='premium'?'complete':'simple',
    value, status:ps, estValue: ps==='recu'?null:value,
    commission:(ps==='recu'||ps==='perdu')?null:Math.round(value*rate/100),
    commissionStatus: m.commissionVerse?'verse':(ps==='effectue'?'valide':'attente'), rate,
    timeline: window.LBC_DATA?window.LBC_DATA.buildTimeline(ps,dates):[],
    submitted:(m._lead&&m._lead.submitted)||iFmtDate(m.createdAt), move:(m._lead&&m._lead.move)||iFmtDate(m.date), notes:m.notes, dates };
}

const I_DIRECT_SEED = [
  { id:'DEM-044', num:'044', createdAt:'2026-06-08', prenom:'fe', nom:'frefre', tel:'06 00 00 00 00', email:'bkredouard@gmail.com', source:'site_web',
    formule:'premium', statut:'devis_a_envoyer', volume:'20', notes:'Lead reçu via le site.', rdvDate:'', rdvHeure:'',
    date:'2026-06-11', heure:'08:00', montant:'750', cout:'', acompte:'', acompteRecu:false, soldeRecu:false,
    dep:iNewSide(), arr:iNewSide() },
  { id:'DEM-039', num:'039', createdAt:'2026-05-28', prenom:'Olivier', nom:'Meyer', tel:'06 78 11 02 90', email:'o.meyer@email.fr', source:'google',
    formule:'standard', statut:'devis_envoye', volume:'15', notes:'', rdvDate:'2026-06-02', rdvHeure:'14:30',
    date:'2026-06-20', heure:'09:30', montant:'1350', cout:'600', acompte:'400', acompteRecu:false, soldeRecu:false,
    dep:{ rue:'2 av. de la Costa', ville:'Monaco', cp:'98000', etage:'2', ascenseur:false, portage:'10', parking:false, notes:'' },
    arr:{ rue:'30 bd Carnot', ville:'Cannes', cp:'06400', etage:'4', ascenseur:true, portage:'8', parking:false, notes:'' } },
  { id:'DEM-035', num:'035', createdAt:'2026-05-12', prenom:'Julie', nom:'Garnier', tel:'06 09 88 71 26', email:'julie@email.fr', source:'bouche_a_oreille',
    formule:'premium', statut:'termine', volume:'20', notes:'Cliente très satisfaite.', rdvDate:'', rdvHeure:'',
    date:'2026-06-03', heure:'07:30', montant:'2200', cout:'980', acompte:'660', acompteRecu:true, soldeRecu:true, avis:5,
    dep:{ rue:'7 av. de la Gare', ville:'Cagnes-sur-Mer', cp:'06800', etage:'1', ascenseur:false, portage:'12', parking:true, notes:'' },
    arr:{ rue:'3 rue Masséna', ville:'Nice', cp:'06000', etage:'2', ascenseur:true, portage:'8', parking:true, notes:'' } },
  { id:'DEM-032', num:'032', createdAt:'2026-04-30', prenom:'Karim', nom:'Naceri', tel:'06 55 12 89 30', email:'karim@email.fr', source:'leboncoin',
    formule:'premium', statut:'termine', volume:'22', notes:'', rdvDate:'', rdvHeure:'',
    date:'2026-05-22', heure:'08:00', montant:'2600', cout:'1200', acompte:'780', acompteRecu:true, soldeRecu:true, avis:4,
    dep:{ rue:'18 rue Barla', ville:'Nice', cp:'06300', etage:'2', ascenseur:false, portage:'18', parking:true, notes:'' },
    arr:{ rue:'25 rue de la République', ville:'Lyon', cp:'69002', etage:'3', ascenseur:true, portage:'10', parking:true, notes:'' } },
  { id:'DEM-030', num:'030', createdAt:'2026-05-26', prenom:'Nadia', nom:'Benali', tel:'07 70 41 33 88', email:'nadia@email.fr', source:'google',
    formule:'eco', statut:'refuse', volume:'10', notes:'Budget trop serré.', rdvDate:'', rdvHeure:'',
    date:'2026-06-15', heure:'08:00', montant:'990', cout:'', acompte:'', acompteRecu:false, soldeRecu:false,
    dep:{ rue:'', ville:'Le Cannet', cp:'06110', etage:'2', ascenseur:false, portage:'', parking:false, notes:'' },
    arr:{ rue:'', ville:'Toulon', cp:'83000', etage:'1', ascenseur:false, portage:'', parking:false, notes:'' } },
];
const I_MOVES_SEED = [
  ...I_DIRECT_SEED,
  ...((window.LBC_DATA&&window.LBC_DATA.RAW_LEADS)?window.LBC_DATA.RAW_LEADS.map(rawLeadToMove):[]),
  ...((window.LBC_ADMIN&&window.LBC_ADMIN.NET_LEADS)?window.LBC_ADMIN.NET_LEADS.filter(l=>l.partner!=='Agence Riviera Immobilier').map(netLeadToMove):[]),
];

/* ---------- SEED ---------- */
const I_SEED = {
  moves:[], tasks:[], objectifs:[], camions:[], equipe:[], materiel:[], charges:[], incidents:[], documents:[], boxes:[],
  settings:{ entreprise:'LBC Déménagement', ownerName:'', email:'contact@lbcdemenagement.com', tel:'06 00 00 00 00',
    adresse:'', cp:'', ville:'Nice', siret:'', tva:'', iban:'', objectifCA:20000, coutDefautPct:45, prixLitre:1.80, consoDefaut:25,
    prixM3:40, prixKmVente:1.2, peageKm:0.18, cplCible:0 },
};

/* ---------- STORE ---------- */
const I_LS = 'lbc_interne_v4'; // v4 : app vidée (aucune donnée de démo)
function iLoad(){ const base=JSON.parse(JSON.stringify(I_SEED));
  try{ const r=localStorage.getItem(I_LS); if(r){ const d=JSON.parse(r); if(d&&d.moves){ Object.keys(base).forEach(k=>{ if(d[k]===undefined) d[k]=base[k]; }); return d; } } }catch(e){}
  return base; }
const I_STORE = { db:iLoad(), subs:new Set() };
function iPersist(){ try{ localStorage.setItem(I_LS, JSON.stringify(I_STORE.db)); }catch(e){} }
function iCommit(fn){ fn(I_STORE.db); iPersist(); I_STORE.subs.forEach(s=>s()); }
function iReset(){ I_STORE.db=JSON.parse(JSON.stringify(I_SEED)); iPersist(); I_STORE.subs.forEach(s=>s()); }
function useInt(){ const [,f]=uIR(x=>x+1,0); uIE(()=>{ const s=()=>f(); I_STORE.subs.add(s); return ()=>I_STORE.subs.delete(s); },[]); return I_STORE.db; }

/* CA & bénéfice pilotés par le STATUT (synchro garantie) :
   - Terminé        -> CA = montant complet, bénéfice = montant - coût (réalisé)
   - En cours       -> CA = ce qui est déjà encaissé (acompte/solde reçus), bénéfice = 0 (pas encore réalisé)
   - Refusé/Annulé  -> 0 partout (retour en arrière annule donc le bénéfice et le CA) */
function iCaEncaisse(m){
  if(['refuse','annule'].includes(m.statut)) return 0;
  if(m.statut==='termine') return iNum(m.montant);
  let v=0; if(m.acompteRecu) v+=iNum(m.acompte);
  if(m.soldeRecu) v+=iNum(m.montant)-iNum(m.acompte);
  return v;
}
function iBenefice(m){ if(m.statut!=='termine') return 0; return iNum(m.montant)-iCoutTotal(m); }

/* ---------- petits composants ---------- */
function IPill({ s, big }){ const c=I_STATUS[s]||I_STATUS.annule; return (
  <span style={{ display:'inline-flex', alignItems:'center', gap:6, fontSize:big?12.5:11.5, fontWeight:600, padding:big?'4px 11px':'3px 9px', borderRadius:999, background:c.bg, color:c.c, whiteSpace:'nowrap' }}>
    <span style={{ width:6, height:6, borderRadius:'50%', background:c.c }} />{c.label}</span>); }
function IFormule({ f }){ const fo=I_FORMULES[f]||I_FORMULES.standard; return (
  <span style={{ fontSize:12.5, fontWeight:600, color:fo.c }}>{fo.label}</span>); }
function IEmpty({ children, pad=28 }){ return <div style={{ textAlign:'center', color:'var(--muted)', fontSize:13, padding:pad+'px 12px', fontStyle:'italic' }}>{children}</div>; }
function IField({ label, children }){ return <div className="field" style={{ display:'flex', flexDirection:'column', gap:6 }}><label style={{ fontSize:12.5, fontWeight:600, color:'var(--ink-2)' }}>{label}</label>{children}</div>; }
function ITabs({ value, onChange, tabs }){ return (
  <div style={{ display:'inline-flex', gap:4, background:'var(--bg)', border:'1px solid var(--border)', borderRadius:10, padding:4 }}>
    {tabs.map(t=>(<button key={t.k} onClick={()=>onChange(t.k)} style={{ fontSize:13, fontWeight:600, padding:'7px 14px', borderRadius:7,
      color:value===t.k?'var(--ink)':'var(--muted)', background:value===t.k?'var(--card)':'transparent', boxShadow:value===t.k?'var(--shadow-xs)':'none' }}>{t.label}</button>))}
  </div>); }
const inpStyle = { height:40, width:'100%', borderRadius:9, border:'1px solid var(--border-2)', background:'var(--card)', padding:'0 12px', fontSize:14, color:'var(--ink)' };
function IInput(p){ return <input {...p} style={{ ...inpStyle, ...(p.style||{}) }} />; }
function ISelect({ value, onChange, children, style }){ return <select value={value} onChange={onChange} style={{ ...inpStyle, ...style }}>{children}</select>; }
function ITextarea(p){ return <textarea {...p} style={{ width:'100%', minHeight:74, borderRadius:9, border:'1px solid var(--border-2)', background:'var(--card)', padding:'10px 12px', fontSize:14, color:'var(--ink)', resize:'vertical', ...(p.style||{}) }} />; }

/* ---------- DRAWER générique ---------- */
function IDrawer({ title, sub, onClose, footer, width=560, centered, children }){
  const overlay = centered
    ? { position:'fixed', inset:0, zIndex:1000, background:'rgba(11,31,43,.5)', display:'flex', alignItems:'center', justifyContent:'center', padding:24 }
    : { position:'fixed', inset:0, zIndex:1000, background:'rgba(11,31,43,.5)', display:'flex', justifyContent:'flex-end' };
  const card = centered
    ? { width:'min('+width+'px,100%)', maxHeight:'90vh', background:'var(--card)', borderRadius:16, display:'flex', flexDirection:'column', boxShadow:'var(--shadow-pop)', overflow:'hidden' }
    : { width:'min('+width+'px,100%)', background:'var(--card)', height:'100%', display:'flex', flexDirection:'column', boxShadow:'var(--shadow-pop)' };
  const node = (
    <div onMouseDown={e=>{ if(e.target===e.currentTarget) onClose(); }} style={overlay}>
      <div onMouseDown={e=>e.stopPropagation()} style={card}>
        <div style={{ padding:'18px 22px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, flexShrink:0 }}>
          <div><div style={{ fontSize:16, fontWeight:700, color:'var(--ink)' }}>{title}</div>{sub && <div style={{ fontSize:12.5, color:'var(--muted)', marginTop:2 }}>{sub}</div>}</div>
          <button onClick={onClose} style={{ color:'var(--muted)', display:'flex', padding:7, borderRadius:8 }}><Icon name="x" size={19} /></button>
        </div>
        <div className="scroll" style={{ flex:1, minHeight:0, overflowY:'auto', padding:'20px 22px' }}>{children}</div>
        {footer && <div style={{ padding:'14px 22px', borderTop:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, flexShrink:0 }}>{footer}</div>}
      </div>
    </div>
  );
  return (window.ReactDOM && ReactDOM.createPortal) ? ReactDOM.createPortal(node, document.body) : node;
}

/* ============================================================ MOVE DRAWER */
function IMoveDrawer({ move, onClose, showToast }){
  const db=useInt(); const isNew=!db.moves.some(x=>x.id===move.id);
  const [m,setM]=uIS(()=>JSON.parse(JSON.stringify(move)));
  const f=(k,v)=>setM(s=>({...s,[k]:v}));
  const fs=(side,k,v)=>setM(s=>({...s,[side]:{...s[side],[k]:v}}));
  const [distLoading,setDistLoading]=uIS(false);
  // Géocode une adresse (OpenStreetMap / Nominatim) → {lat,lon}
  const iGeocode=async(q)=>{ const r=await fetch('https://nominatim.openstreetmap.org/search?format=json&limit=1&q='+encodeURIComponent(q),{ headers:{ 'Accept':'application/json' } }); const j=await r.json(); return (j&&j.length)?{ lat:parseFloat(j[0].lat), lon:parseFloat(j[0].lon) }:null; };
  // Calcule la distance routière entre départ et arrivée, puis pré-remplit km + péage estimé
  const calcDistance=async()=>{
    if(!m.dep.ville && !m.dep.rue || !m.arr.ville && !m.arr.rue){ showToast('Renseigne les adresses (au moins les villes)'); return; }
    const da=[m.dep.rue,m.dep.cp,m.dep.ville,'France'].filter(Boolean).join(', ');
    const aa=[m.arr.rue,m.arr.cp,m.arr.ville,'France'].filter(Boolean).join(', ');
    setDistLoading(true);
    try{
      // 1) Proxy serveur (péage EXACT via Google Routes) — dispo sur l'app déployée
      try{
        const rr=await fetch('/api/route?from='+encodeURIComponent(da)+'&to='+encodeURIComponent(aa));
        if(rr.ok){ const jj=await rr.json(); if(jj && !jj.error && jj.km){
          setM(s=>({ ...s, km:String(jj.km), peage:String(jj.peage!=null?jj.peage:iPeageEstime(jj.km)) }));
          showToast('📍 '+jj.km+' km — péage '+(jj.peage!=null?euro(jj.peage)+' (exact)':'estimé'));
          setDistLoading(false); return; } }
      }catch(e){}
      // 2) Repli : distance via OpenStreetMap + péage estimé (si le proxy n'est pas dispo)
      const [g1,g2]=await Promise.all([iGeocode(da),iGeocode(aa)]);
      if(!g1||!g2){ showToast('Adresse introuvable — précise ville + code postal'); setDistLoading(false); return; }
      const u='https://router.project-osrm.org/route/v1/driving/'+g1.lon+','+g1.lat+';'+g2.lon+','+g2.lat+'?overview=false';
      const r=await fetch(u); const j=await r.json();
      if(!j.routes||!j.routes.length){ showToast('Itinéraire introuvable'); setDistLoading(false); return; }
      const km=Math.round(j.routes[0].distance/1000);
      setM(s=>({ ...s, km:String(km), peage:String(iPeageEstime(km)) }));
      showToast('📍 '+km+' km — essence et péage estimés');
    }catch(e){ showToast('Calcul impossible (vérifie ta connexion)'); }
    setDistLoading(false);
  };
  const save=()=>{ if(!m.prenom.trim()&&!m.nom.trim()){ showToast('Indiquez au moins un nom de client'); return; }
    iCommit(db=>{ const i=db.moves.findIndex(x=>x.id===m.id); if(i>=0) db.moves[i]=m; else db.moves.unshift(m); });
    showToast(isNew?'Déménagement créé':'Modifications enregistrées'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.moves=db.moves.filter(x=>x.id!==m.id); }); showToast('Déménagement supprimé'); onClose(); };
  const setStatut=(s)=>{ const was=m.statut; f('statut',s); iCommit(db=>{ const i=db.moves.findIndex(x=>x.id===m.id); if(i>=0){ db.moves[i].statut=s; iStampStatut(db.moves[i], s); } });
    if(s==='termine'&&was!=='termine'){ iCelebrate(m.montant); return; }
    if(s==='devis_envoye'&&was!=='devis_envoye'&&m.email&&window.LBC_SB&&window.LBC_SB.inviteClient){ window.LBC_SB.inviteClient(m.email).then(r=>showToast(r&&r.ok?('Devis envoyé · invitation envoyée à '+m.email):'Devis envoyé (invitation non partie)')); return; }
    showToast('Statut → '+I_STATUS[s].label);
  };

  const side=(label,key)=>(
    <div style={{ marginTop:18 }}>
      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', marginBottom:10 }}>{label}</div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
        <IField label="Ville"><IInput value={m[key].ville} onChange={e=>fs(key,'ville',e.target.value)} placeholder="Nice" /></IField>
        <IField label="Code postal"><IInput value={m[key].cp} onChange={e=>fs(key,'cp',e.target.value)} placeholder="06000" /></IField>
      </div>
      <div style={{ marginTop:12 }}><IField label="Adresse"><IInput value={m[key].rue} onChange={e=>fs(key,'rue',e.target.value)} placeholder="12 rue des Lilas" /></IField></div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:12, marginTop:12 }}>
        <IField label="Étage"><IInput type="number" min="0" value={m[key].etage} onChange={e=>fs(key,'etage',e.target.value)} /></IField>
        <IField label="Ascenseur"><ISelect value={m[key].ascenseur?'1':'0'} onChange={e=>fs(key,'ascenseur',e.target.value==='1')}><option value="0">Non</option><option value="1">Oui</option></ISelect></IField>
        <IField label="Portage (m)"><IInput type="number" min="0" value={m[key].portage} onChange={e=>fs(key,'portage',e.target.value)} /></IField>
      </div>
      {m[key].ascenseur && <div style={{ marginTop:12 }}><IField label="Taille de l'ascenseur"><ISelect value={m[key].ascTaille||'Aucun'} onChange={e=>fs(key,'ascTaille',e.target.value)}>{I_ASC_TAILLES.map(t=><option key={t}>{t}</option>)}</ISelect></IField></div>}
      <label style={{ display:'flex', alignItems:'center', gap:9, marginTop:12, fontSize:13.5, color:'var(--ink-2)', cursor:'pointer' }}>
        <input type="checkbox" checked={m[key].parking} onChange={e=>fs(key,'parking',e.target.checked)} />Emplacement camion réservé</label>
    </div>
  );

  return (
    <IDrawer title={(m.prenom||m.nom)?((m.prenom||'')+' '+(m.nom||'')).trim():'Nouveau déménagement'} sub={m.id} onClose={onClose} centered width={640}
      footer={<>
        <div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
          {!isNew && <button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}><Icon name="x" size={14} />Supprimer</button>}
          {m.email && window.LBC_SB && window.LBC_SB.inviteClient && <button className="btn btn-ghost btn-sm" onClick={async()=>{ const r=await window.LBC_SB.inviteClient(m.email); showToast(r&&r.ok?('Invitation envoyée à '+m.email):('Échec invitation — '+((r&&r.error)||'?'))); }} title="Crée son compte + lui envoie un lien d'accès"><Icon name="mail" size={14} />Inviter le client</button>}
        </div>
        <div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}><Icon name="check" size={14} />Enregistrer</button></div>
      </>}>
      {!isNew && <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
        <IPill s={m.statut} big />
        <ISelect value={m.statut} onChange={e=>setStatut(e.target.value)} style={{ height:34, width:'auto', fontSize:13 }}>
          {Object.keys(I_STATUS).map(k=><option key={k} value={k}>{I_STATUS[k].label}</option>)}
        </ISelect>
      </div>}

      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', margin:'20px 0 10px' }}>Client</div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
        <IField label="Prénom"><IInput value={m.prenom} onChange={e=>f('prenom',e.target.value)} /></IField>
        <IField label="Nom"><IInput value={m.nom} onChange={e=>f('nom',e.target.value)} /></IField>
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:12 }}>
        <IField label="Téléphone"><IInput value={m.tel} onChange={e=>f('tel',e.target.value)} /></IField>
        <IField label="Email"><IInput value={m.email} onChange={e=>f('email',e.target.value)} /></IField>
      </div>
      <div style={{ marginTop:12 }}>
        <IField label="Source du lead"><ISelect value={m.source} onChange={e=>f('source',e.target.value)}>{Object.keys(I_SOURCES).map(k=><option key={k} value={k}>{I_SOURCES[k]}</option>)}</ISelect></IField>
      </div>
      <div style={{ marginTop:14 }}>
        <IField label="Formule">
          <div style={{ display:'grid', gridTemplateColumns:'repeat(3,1fr)', gap:8 }}>
            {Object.keys(I_FORMULES).map(k=>{ const fo=I_FORMULES[k]; const on=m.formule===k; return (
              <button type="button" key={k} onClick={()=>f('formule',k)} style={{ textAlign:'left', padding:'11px 12px', borderRadius:12, cursor:'pointer', border:'1.5px solid '+(on?fo.c:'var(--border-2)'), background:on?fo.c:'var(--card)', color:on?'#fff':'var(--ink)', boxShadow:on?'0 4px 14px '+fo.c+'44':'none', transition:'.14s' }}>
                <div style={{ fontSize:14, fontWeight:800 }}>{fo.label}</div>
                <div style={{ fontSize:10, opacity:on?.92:.6, marginTop:3, lineHeight:1.25 }}>{fo.desc}</div>
              </button>); })}
          </div>
        </IField>
      </div>

      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', margin:'20px 0 10px' }}>Planning</div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:12 }}>
        <IField label="Volume (m³)"><div style={{ display:'flex', gap:6 }}><IInput type="number" value={m.volume} onChange={e=>f('volume',e.target.value)} style={{ flex:1, minWidth:0 }} />{(m.inventaire&&m.inventaire.length)?<button type="button" onClick={()=>f('volume',String(iInvTotal(m)))} className="btn btn-ghost btn-sm" style={{ whiteSpace:'nowrap', flexShrink:0 }} title="Reprendre le volume calculé depuis l'inventaire">∑ {iInvTotal(m)}</button>:null}</div></IField>
        <IField label="Date déménagement"><IInput type="date" value={m.date} onChange={e=>f('date',e.target.value)} /></IField>
        <IField label="Heure"><IInput type="time" value={m.heure} onChange={e=>f('heure',e.target.value)} /></IField>
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:12 }}>
        <IField label="Date RDV devis"><IInput type="date" value={m.rdvDate} onChange={e=>f('rdvDate',e.target.value)} /></IField>
        <IField label="Heure RDV"><IInput type="time" value={m.rdvHeure} onChange={e=>f('rdvHeure',e.target.value)} /></IField>
      </div>

      {side('📍 Départ','dep')}
      {side('📍 Arrivée','arr')}

      {(()=>{
        const PIECES=['Salon & séjour','Chambre','Chambre 2','Cuisine','Salle à manger','Salle de bain','Entrée / couloir','Bureau','Garage','Cave / grenier','Extérieur','Autre'];
        const inv=m.inventaire||[];
        const setInv=(arr)=>f('inventaire',arr);
        const addInv=()=>setInv([...inv, { piece:'', meuble:'', quantite:1, volume:'' }]);
        const upInv=(i,k,v)=>{ const a=inv.map((x,j)=>j===i?{...x,[k]:v}:x); if(k==='meuble' && (a[i].volume===''||a[i].volume==null)){ const vv=iMeubleVol(v); if(vv) a[i]={...a[i],volume:vv}; } setInv(a); };
        const delInv=(i)=>setInv(inv.filter((_,j)=>j!==i));
        const ChipPicker=({label,k,color,opts})=>{ const arr=m[k]||[];
          const found=(o)=>arr.find(x=>(x.label||x)===o);
          const toggle=(o)=>{ found(o)?f(k,arr.filter(x=>(x.label||x)!==o)):f(k,[...arr,{label:o,qty:1}]); };
          const setQty=(o,q)=>f(k,arr.map(x=>(x.label||x)===o?{...x,qty:Math.max(1,q)}:x));
          return (
          <div style={{ marginBottom:14 }}>
            <div style={{ fontSize:11, fontWeight:700, color:color, textTransform:'uppercase', letterSpacing:'.03em', marginBottom:8 }}>{label}</div>
            <div style={{ display:'flex', gap:7, flexWrap:'wrap' }}>{opts.map(o=>{ const sel=found(o); const qty=sel?(iNum(sel.qty)||1):0; return (
              <div key={o} style={{ display:'inline-flex', alignItems:'center', gap:6, padding:sel?'3px 7px 3px 11px':'7px 11px', borderRadius:999, border:'1.5px solid '+(sel?color:'var(--border-2)'), background:sel?color+'18':'var(--card)', color:sel?color:'var(--ink-2)', fontSize:12.5, fontWeight:600 }}>
                <span onClick={()=>toggle(o)} style={{ cursor:'pointer' }}>{sel?'✓ ':''}{o}</span>
                {sel && <span style={{ display:'inline-flex', alignItems:'center', gap:4 }}>
                  <button type="button" onClick={()=>setQty(o,qty-1)} style={{ width:18, height:18, borderRadius:5, border:'1px solid '+color, background:'transparent', color:color, cursor:'pointer', lineHeight:1 }}>−</button>
                  <span style={{ minWidth:10, textAlign:'center' }}>{qty}</span>
                  <button type="button" onClick={()=>setQty(o,qty+1)} style={{ width:18, height:18, borderRadius:5, border:'1px solid '+color, background:'transparent', color:color, cursor:'pointer', lineHeight:1 }}>+</button>
                </span>}
              </div>); })}</div>
          </div>); };
        return (
        <div style={{ marginTop:20, border:'1px solid var(--border)', borderRadius:12, overflow:'hidden' }}>
          <div style={{ padding:'12px 14px', background:'var(--bg)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
            <span style={{ fontSize:13, fontWeight:700, color:'var(--ink)' }}>📋 Détails & inventaire</span>
            {m.source==='site_web' && m.formulaireType && <span style={{ fontSize:11, fontWeight:700, color:'#2563EB', background:'rgba(37,99,235,.12)', padding:'2px 9px', borderRadius:999 }}>{m.formulaireType==='detaille'?'Formulaire détaillé':'Formulaire basique'}</span>}
          </div>
          <div style={{ padding:'14px' }}>
            <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:12, marginBottom:16 }}>
              <IField label="Cartons (nombre)"><IInput type="number" value={m.cartons} onChange={e=>f('cartons',e.target.value)} placeholder="0" /></IField>
              <IField label="Flexibilité date"><IInput value={m.flexibilite} onChange={e=>f('flexibilite',e.target.value)} placeholder="Ex : Flexible ±3 j" /></IField>
              <IField label="Contact préféré"><IInput value={m.contactPref} onChange={e=>f('contactPref',e.target.value)} placeholder="Téléphone, email…" /></IField>
            </div>
            <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8 }}>
              <span style={{ fontSize:11, fontWeight:700, color:'var(--muted)', textTransform:'uppercase', letterSpacing:'.03em' }}>🪑 Inventaire des meubles {iInvTotal(m)>0?<span style={{ color:'var(--ink-2)' }}>· {iInvTotal(m)} m³ estimés</span>:null}</span>
              <button type="button" className="btn btn-ghost btn-sm" onClick={addInv}><Icon name="plus" size={13} />Ajouter un meuble</button>
            </div>
            <datalist id="lbc-meubles">{I_SITE_MEUBLES.map(k=><option key={k} value={k} />)}</datalist>
            {inv.length===0 ? <div style={{ fontSize:12.5, color:'var(--muted)', fontStyle:'italic', marginBottom:6 }}>Aucun meuble. Ajoute-les un par un, le volume est estimé automatiquement.</div> : (
              <div style={{ marginBottom:6 }}>
                <div style={{ display:'flex', gap:8, fontSize:9.5, fontWeight:700, color:'var(--muted)', textTransform:'uppercase', letterSpacing:'.03em', padding:'0 2px 5px' }}>
                  <span style={{ flex:'1 1 30%' }}>Pièce</span><span style={{ flex:'1 1 40%' }}>Meuble</span><span style={{ width:54, textAlign:'center', flexShrink:0 }}>Qté</span><span style={{ width:70, textAlign:'center', flexShrink:0 }}>m³ / u</span><span style={{ width:24, flexShrink:0 }} />
                </div>
                {inv.map((it,i)=>(<div key={i} style={{ display:'flex', gap:8, alignItems:'center', marginBottom:7 }}>
                  <ISelect value={it.piece||''} onChange={e=>upInv(i,'piece',e.target.value)} style={{ flex:'1 1 30%', minWidth:0, height:34, fontSize:12.5, padding:'0 6px' }}><option value="">—</option>{PIECES.map(p=><option key={p}>{p}</option>)}</ISelect>
                  <IInput list="lbc-meubles" value={it.meuble||''} onChange={e=>upInv(i,'meuble',e.target.value)} placeholder="Ex : Canapé 3 places" style={{ flex:'1 1 40%', minWidth:0, height:34, fontSize:12.5 }} />
                  <IInput type="number" min="1" value={it.quantite||1} onChange={e=>upInv(i,'quantite',iNum(e.target.value)||1)} style={{ width:54, flexShrink:0, height:34, textAlign:'center', padding:'0 6px' }} title="Quantité" />
                  <IInput type="number" step="0.1" value={(it.volume!=null&&it.volume!=='')?it.volume:''} onChange={e=>upInv(i,'volume',e.target.value)} placeholder={String(iMeubleVol(it.meuble)||'')} title="Volume estimé automatiquement — modifiable" style={{ width:70, flexShrink:0, height:34, textAlign:'center', padding:'0 6px' }} />
                  <button type="button" onClick={()=>delInv(i)} style={{ width:24, flexShrink:0, color:'var(--muted)', display:'flex', justifyContent:'center', padding:4 }}><Icon name="x" size={14} /></button>
                </div>))}
              </div>)}
            {iInvTotal(m)>0 && <button type="button" className="btn btn-ghost btn-sm" onClick={()=>f('volume',String(iInvTotal(m)))} style={{ marginBottom:16 }}>↥ Reprendre {iInvTotal(m)} m³ comme volume du déménagement</button>}
            <div style={{ height:1, background:'var(--border)', margin:'8px 0 16px' }} />
            <ChipPicker label="🛡 Objets fragiles à protéger" k="fragiles" color="#B63D24" opts={I_SITE_FRAGILES} />
            <ChipPicker label="🔧 À démonter / remonter" k="demonter" color="#6D4AC4" opts={I_SITE_DEMONTAGE} />
          </div>
        </div>); })()}

      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', margin:'22px 0 10px' }}>🚚 Le trajet</div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
        <IField label="Distance (km)"><div style={{ display:'flex', gap:6 }}><IInput type="number" value={m.km} onChange={e=>f('km',e.target.value)} placeholder="ex : 120" style={{ flex:1 }} /><button type="button" onClick={calcDistance} disabled={distLoading} className="btn btn-ghost btn-sm" style={{ whiteSpace:'nowrap', flexShrink:0 }} title="Calcule la distance routière entre les 2 adresses">{distLoading?'⏳…':'📍 Calculer'}</button></div></IField>
        <div style={{ display:'flex', flexDirection:'column', gap:6 }}><label style={{ fontSize:12.5, fontWeight:600, color:'var(--ink-2)' }}>Trajet</label>
          <label style={{ display:'flex', alignItems:'center', gap:9, height:40, fontSize:13.5, color:'var(--ink-2)', cursor:'pointer' }}><input type="checkbox" checked={m.allerRetour} onChange={e=>f('allerRetour',e.target.checked)} />Aller-retour (×2 sur l'essence)</label></div>
      </div>

      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, marginTop:12, padding:'12px 14px', borderRadius:11, background:'rgba(199,122,10,.08)', border:'1px solid rgba(199,122,10,.25)' }}>
        <div><div style={{ fontSize:12.5, fontWeight:700, color:'var(--ink)' }}>⛽ Coût essence (calculé auto)</div>
          <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:2 }}>{iNum(m.km)?(iNum(m.km)+' km'+(m.allerRetour?' ×2':'')+' · '+((m.camions||[]).length?(m.camions.length+' camion(s)'):'conso par défaut')):'Renseigne la distance ci-dessus'}</div></div>
        <div className="tnum" style={{ fontSize:22, fontWeight:800, color:'#C77A0A' }}>{euro(iEssence(m))}</div>
      </div>

      <div style={{ display:'flex', gap:12, alignItems:'flex-end', marginTop:12 }}>
        <div style={{ flex:1 }}><IField label="Péage (€)"><IInput type="number" value={m.peage} onChange={e=>f('peage',e.target.value)} placeholder="ex : 65" /></IField></div>
        <label style={{ display:'flex', alignItems:'center', gap:8, height:42, fontSize:13.5, color:'var(--ink-2)', cursor:'pointer', whiteSpace:'nowrap' }}><input type="checkbox" checked={!!m.peageAR} onChange={e=>f('peageAR',e.target.checked)} />Aller-retour (×2)</label>
      </div>

      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', margin:'22px 0 10px' }}>🚛 Camions affectés</div>
      <div>
        <div style={{ display:'flex', justifyContent:'flex-end', alignItems:'center', marginBottom:8 }}>
          <button type="button" className="btn btn-ghost btn-sm" onClick={()=>f('camions',[...(m.camions||[]), { id:'loue-'+Date.now(), loue:true, nom:'Camion loué', volume:'', location:'' }])}><Icon name="plus" size={13} />Camion loué</button>
        </div>
        {db.camions.length>0 && <div style={{ display:'flex', gap:7, flexWrap:'wrap', marginBottom:8 }}>{db.camions.map(c=>{ const on=(m.camions||[]).some(x=>x.id===c.id); return (
          <button type="button" key={c.id} onClick={()=>{ const cur=m.camions||[]; f('camions', on?cur.filter(x=>x.id!==c.id):[...cur,{ id:c.id, loue:false, nom:c.nom, volume:c.volume }]); }} style={{ fontSize:12, fontWeight:600, padding:'6px 11px', borderRadius:999, border:'1px solid', borderColor:on?'var(--navy)':'var(--border-2)', background:on?'var(--navy)':'var(--card)', color:on?'#fff':'var(--ink-2)' }}>🚛 {c.nom}{c.volume?' · '+c.volume+'m³':''}</button>); })}</div>}
        {(m.camions||[]).length===0 ? <IEmpty pad={10}>Aucun camion. Choisis dans ta flotte ci-dessus ou ajoute un camion loué.</IEmpty> :
          <div style={{ display:'flex', flexDirection:'column', gap:6 }}>{(m.camions||[]).map((c,i)=>(
            <div key={i} style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 10px', borderRadius:9, background:'var(--bg)' }}>
              <Icon name="truck" size={15} style={{ color:c.loue?'#C77A0A':'var(--navy)', flexShrink:0 }} />
              {c.loue ? <IInput value={c.nom} onChange={e=>{ const a=[...m.camions]; a[i]={...c,nom:e.target.value}; f('camions',a); }} placeholder="Camion loué" style={{ flex:1, minWidth:0, height:32 }} /> : <span style={{ flex:1, fontSize:13, fontWeight:600 }}>{c.nom}{c.volume?' · '+c.volume+'m³':''}</span>}
              {c.loue ? <React.Fragment><IInput type="number" value={c.volume} onChange={e=>{ const a=[...m.camions]; a[i]={...c,volume:e.target.value}; f('camions',a); }} placeholder="m³" title="Volume du camion (m³)" style={{ width:50, height:32, flexShrink:0, textAlign:'center', padding:'0 5px' }} /><IInput type="number" value={c.chargeUtile} onChange={e=>{ const a=[...m.camions]; a[i]={...c,chargeUtile:e.target.value}; f('camions',a); }} placeholder="kg" title="Charge utile (kg)" style={{ width:58, height:32, flexShrink:0, textAlign:'center', padding:'0 5px' }} /><IInput type="number" value={c.location} onChange={e=>{ const a=[...m.camions]; a[i]={...c,location:e.target.value}; f('camions',a); }} placeholder="Loc.€" title="Prix de location" style={{ width:66, height:32, flexShrink:0 }} /><IInput type="number" value={c.conso} onChange={e=>{ const a=[...m.camions]; a[i]={...c,conso:e.target.value}; f('camions',a); }} placeholder="L/100" title="Consommation (L/100km)" style={{ width:60, height:32, flexShrink:0 }} /></React.Fragment> : <span style={{ fontSize:11, color:'#16965A', whiteSpace:'nowrap' }}>à toi · km suivis</span>}
              <button type="button" onClick={()=>f('camions', m.camions.filter((_,j)=>j!==i))} style={{ color:'var(--muted)', display:'flex' }}><Icon name="x" size={14} /></button>
            </div>))}</div>}
        {(m.camions||[]).length>0 && (()=>{
          const capNom=(m.camions||[]).reduce((a,c)=>a+iNum(c.volume),0);
          const capUtile=Math.round(capNom*0.75); const charge=iNum(m.volume); const overV=charge>0 && charge>capUtile;
          const capKg=(m.camions||[]).reduce((a,c)=>a+iChargeUtile(c),0); const poids=iPoidsTotal(m); const overP=capKg>0 && poids>capKg;
          if(!capNom && !capKg) return <div style={{ marginTop:9, fontSize:11.5, color:'var(--muted)', fontStyle:'italic' }}>Renseigne le volume et la charge utile des camions (dans Opérationnel) pour vérifier capacité & surcharge.</div>;
          const over=overV||overP;
          return <div style={{ marginTop:10, fontSize:12, padding:'9px 12px', borderRadius:9, fontWeight:600, lineHeight:1.5, background:over?'rgba(194,54,43,.10)':'rgba(22,150,90,.10)', color:over?'#C2362B':'#16965A' }}>
            {capNom>0 && <div>{overV?'⚠ Volume : ':'✓ Volume : '}{charge||'?'} m³ / {capUtile} m³ utiles ({capNom} m³ × 75 %){overV?' — camion en plus ou 2ᵉ voyage':''}</div>}
            {capKg>0 && <div style={{ marginTop:capNom>0?4:0 }}>{overP?'⚠ Poids : ':'✓ Poids : '}~{poids} kg / {capKg} kg de charge utile{overP?' — SURCHARGE, répartis la charge ou ajoute un camion':''}</div>}
          </div>; })()}
      </div>

      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', margin:'22px 0 10px' }}>
        <span style={{ fontSize:13, fontWeight:700, color:'var(--ink)' }}>👷 Main d'œuvre</span>
        <button type="button" className="btn btn-ghost btn-sm" onClick={()=>f('mainOeuvre',[...(m.mainOeuvre||[]), { id:'', nom:'', cout:'' }])}><Icon name="plus" size={13} />Personne externe</button>
      </div>
      <div>
        {db.equipe.length>0 && <div style={{ display:'flex', gap:7, flexWrap:'wrap', marginBottom:8 }}>{db.equipe.map(e=>{ const on=(m.mainOeuvre||[]).some(p=>p.id===e.id); return (
          <button type="button" key={e.id} onClick={()=>{ const cur=m.mainOeuvre||[]; f('mainOeuvre', on?cur.filter(p=>p.id!==e.id):[...cur,{ id:e.id, nom:e.prenom+' '+e.nom, cout:'' }]); }} style={{ fontSize:12, fontWeight:600, padding:'6px 11px', borderRadius:999, border:'1px solid', borderColor:on?'var(--brand)':'var(--border-2)', background:on?'var(--brand)':'var(--card)', color:on?'#fff':'var(--ink-2)' }}>{e.prenom} {e.nom}</button>); })}</div>}
        {(m.mainOeuvre||[]).length===0 ? <IEmpty pad={10}>Aucune personne. Choisis dans ton équipe ci-dessus ou ajoute une personne externe.</IEmpty> :
          <div style={{ display:'flex', flexDirection:'column', gap:6 }}>{(m.mainOeuvre||[]).map((p,i)=>(
            <div key={i} style={{ display:'flex', alignItems:'center', gap:8, padding:'8px 10px', borderRadius:9, background:'var(--bg)' }}>
              <Icon name="user" size={15} style={{ color:'var(--brand)', flexShrink:0 }} />
              {p.id ? <span style={{ flex:1, fontSize:13, fontWeight:600 }}>{p.nom}</span> : <IInput value={p.nom} onChange={e=>{ const a=[...m.mainOeuvre]; a[i]={...p,nom:e.target.value}; f('mainOeuvre',a); }} placeholder="Nom de la personne" style={{ flex:1, height:32 }} />}
              <IInput type="number" value={p.cout} onChange={e=>{ const a=[...m.mainOeuvre]; a[i]={...p,cout:e.target.value}; f('mainOeuvre',a); }} placeholder="Coût €" style={{ width:110, height:32 }} />
              <button type="button" onClick={()=>f('mainOeuvre', m.mainOeuvre.filter((_,j)=>j!==i))} style={{ color:'var(--muted)', display:'flex' }}><Icon name="x" size={14} /></button>
            </div>))}</div>}
      </div>

      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', margin:'22px 0 10px' }}>📦 Frais supplémentaires</div>
      <IField label="Frais matériel (cartons, housses, films…)"><IInput type="number" value={m.fraisMateriel} onChange={e=>f('fraisMateriel',e.target.value)} placeholder="0 €" /></IField>

      <div style={{ marginTop:14 }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8 }}>
          <span style={{ fontSize:12.5, fontWeight:600, color:'var(--ink-2)' }}>➕ Frais annexes (à la ligne)</span>
          <button type="button" className="btn btn-ghost btn-sm" onClick={()=>f('fraisAnnexes',[...(m.fraisAnnexes||[]), { libelle:'', montant:'' }])}><Icon name="plus" size={13} />Ajouter</button>
        </div>
        {(m.fraisAnnexes||[]).map((fa,i)=>(
          <div key={i} style={{ display:'flex', gap:8, marginBottom:6 }}>
            <IInput value={fa.libelle} onChange={e=>{ const a=[...m.fraisAnnexes]; a[i]={...fa,libelle:e.target.value}; f('fraisAnnexes',a); }} placeholder="Libellé (ex : housse matelas)" style={{ flex:1, height:34 }} />
            <IInput type="number" value={fa.montant} onChange={e=>{ const a=[...m.fraisAnnexes]; a[i]={...fa,montant:e.target.value}; f('fraisAnnexes',a); }} placeholder="€" style={{ width:90, height:34 }} />
            <button type="button" onClick={()=>f('fraisAnnexes', m.fraisAnnexes.filter((_,j)=>j!==i))} style={{ color:'var(--muted)', display:'flex' }}><Icon name="x" size={15} /></button>
          </div>))}
      </div>

      <div style={{ marginTop:14, padding:'12px 14px', borderRadius:11, background:'var(--bg)', border:'1px solid var(--border)' }}>
        <div style={{ fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:'.05em', color:'var(--muted)', marginBottom:8 }}>Coût estimé du chantier</div>
        {[['⛽ Essence ('+(iNum(m.km)||0)+' km'+(m.allerRetour?' ×2':'')+')', iEssence(m)],['🛣 Péage'+(m.peageAR?' ×2':''), iNum(m.peage)*(m.peageAR?2:1)],['🚛 Location camion(s)', iLocationCamions(m)],['👷 Main d’œuvre', iManutention(m)],['📦 Matériel', iNum(m.fraisMateriel)],['➕ Frais annexes', iFraisAnnexes(m)]].map(([l,v],i)=>(
          <div key={i} style={{ display:'flex', justifyContent:'space-between', fontSize:12.5, padding:'3px 0', color:'var(--ink-2)' }}><span>{l}</span><span className="tnum">{euro(v)}</span></div>))}
        <div style={{ display:'flex', justifyContent:'space-between', fontSize:14, fontWeight:800, paddingTop:8, marginTop:6, borderTop:'1px solid var(--border)' }}><span>Coût total</span><span className="tnum" style={{ color:'#C77A0A' }}>{euro(iCoutTotal(m))}</span></div>
      </div>

      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', margin:'20px 0 10px' }}>💶 Devis & paiement</div>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, marginBottom:12, padding:'12px 14px', borderRadius:11, background:'rgba(37,99,235,.08)', border:'1px solid rgba(37,99,235,.25)' }}>
        <div><div style={{ fontSize:12.5, fontWeight:700, color:'var(--ink)' }}>💡 Prix conseillé à proposer</div>
          <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:2 }}>{(iNum(m.volume)||iNum(m.km))?((iNum(m.volume)||'?')+' m³ · '+(iNum(m.km)||'?')+' km · '+(I_FORMULES[m.formule]||{}).label+' · coût '+euro(iCoutTotal(m))):'Renseigne le volume et la distance'}</div></div>
        <div style={{ display:'flex', alignItems:'center', gap:10 }}>
          <span className="tnum" style={{ fontSize:22, fontWeight:800, color:'#2563EB' }}>{euro(iPrixConseille(m))}</span>
          <button type="button" className="btn btn-ghost btn-sm" onClick={()=>f('montant',String(iPrixConseille(m)))}>Appliquer</button>
        </div>
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
        <IField label="Montant du devis (€)"><IInput type="number" value={m.montant} onChange={e=>f('montant',e.target.value)} /></IField>
        <IField label="Acompte (€)"><IInput type="number" value={m.acompte} onChange={e=>f('acompte',e.target.value)} /></IField>
      </div>
      <div style={{ display:'flex', gap:20, marginTop:10 }}>
        <label style={{ display:'flex', alignItems:'center', gap:9, fontSize:13.5, color:'var(--ink-2)', cursor:'pointer' }}><input type="checkbox" checked={m.acompteRecu} onChange={e=>f('acompteRecu',e.target.checked)} />Acompte reçu</label>
        <label style={{ display:'flex', alignItems:'center', gap:9, fontSize:13.5, color:'var(--ink-2)', cursor:'pointer' }}><input type="checkbox" checked={m.soldeRecu} onChange={e=>f('soldeRecu',e.target.checked)} />Solde réglé</label>
      </div>
      <div style={{ marginTop:10, fontSize:12.5, color:'var(--muted)' }}>Bénéfice estimé : <b style={{ color:(iNum(m.montant)-iCoutTotal(m))>=0?'#16965A':'#C2362B' }}>{euro(iNum(m.montant)-iCoutTotal(m))}</b> (devis − coût)</div>
      <div style={{ marginTop:18 }}><IField label="Notes"><ITextarea value={m.notes} onChange={e=>f('notes',e.target.value)} placeholder="Informations complémentaires…" /></IField></div>
    </IDrawer>
  );
}

/* ============================================================ ACCUEIL */
function IAccueil({ go, openMove, showToast, editIncident }){
  const db=useInt(); const today=iToday(); const mk=iMonthKey(today);
  const h=new Date().getHours(); const greet=h<12?'Bonjour':h<18?'Bon après-midi':'Bonsoir';
  const mois=db.moves.filter(m=>iMonthKey(m.date)===mk);
  const caMois=mois.reduce((a,m)=>a+iCaEncaisse(m),0);
  const kpis=[
    { label:'CA ce mois', value:euro(caMois), tone:'g' },
    { label:'Déménag. ce mois', value:String(mois.length), tone:'b' },
    { label:'Terminés ce mois', value:String(mois.filter(m=>m.statut==='termine').length), tone:'g' },
    { label:'En attente réponse', value:String(db.moves.filter(m=>m.statut==='devis_envoye').length) },
    { label:'Devis à envoyer', value:String(db.moves.filter(m=>m.statut==='devis_a_envoyer').length), tone:'r' },
    { label:'Refusés ce mois', value:String(mois.filter(m=>m.statut==='refuse').length) },
    { label:'Annulés ce mois', value:String(mois.filter(m=>m.statut==='annule').length) },
  ];
  // alertes : emplacement non réservé J-3
  const alertes=[];
  db.moves.forEach(m=>{ if(['refuse','annule','termine'].includes(m.statut)||!m.date) return;
    const dl=iDaysBetween(today,m.date);
    if(dl>=0&&dl<=3){
      if(!m.dep.parking) alertes.push({ t:'amber', x:`${m.prenom} ${m.nom} — Emplacement départ non réservé`, s:`Dans ${dl} jour${dl>1?'s':''} (${iFmtDate(m.date)})`, m });
      if(!m.arr.parking) alertes.push({ t:'amber', x:`${m.prenom} ${m.nom} — Emplacement arrivée non réservé`, s:`Dans ${dl} jour${dl>1?'s':''} (${iFmtDate(m.date)})`, m });
    }
  });
  iStockBas().forEach(m=>alertes.push({ t:'red', x:'📦 Stock bas : '+m.nom, s:m.unites+' restant(s) · seuil '+m.seuil+' · à renflouer', nav:'i_operationnel' }));
  db.incidents.filter(iIncidentOuvert).forEach(i=>alertes.push({ t:'red', x:'⚠️ Litige à régler : '+i.titre, s:(i.client||'')+' · '+(I_INC_STATUTS[i.statut]||''), inc:i }));
  const aVenir=db.moves.filter(m=>m.date>=today && iDaysBetween(today,m.date)<=30 && ['confirme','attente_paiement'].includes(m.statut)).sort((a,b)=>a.date.localeCompare(b.date));
  const devisAttente=db.moves.filter(m=>m.statut==='devis_envoye');
  const devisAEnvoyer=db.moves.filter(m=>m.statut==='devis_a_envoyer');
  const acomptes=db.moves.filter(m=>iNum(m.acompte)>0 && !m.acompteRecu && !['refuse','annule'].includes(m.statut));
  const paiements=db.moves.filter(m=>m.statut==='attente_paiement' && !m.soldeRecu);
  const taches=db.tasks.filter(t=>!t.done && t.quad==='now');
  const maintRetard=iToutesAlertesFlotte().filter(a=>a.statut==='retard');
  const incidents=db.incidents.filter(iIncidentOuvert);
  const objMois=db.objectifs.filter(o=>o.mois===mk);

  const Card=({ title, count, children, accent })=>(
    <div className="card" style={{ overflow:'hidden', borderTop:accent?'2px solid '+accent:'none' }}>
      <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
        <span className="section-title" style={{ fontSize:14.5 }}>{title}</span>
        {count!=null && <span style={{ fontSize:11.5, fontWeight:700, background:accent||'var(--brand)', color:'#fff', borderRadius:999, minWidth:20, textAlign:'center', padding:'1px 7px' }}>{count}</span>}
      </div>
      <div style={{ padding:'8px 10px' }}>{children}</div>
    </div>
  );
  const Row=({ m, right })=>(
    <div onClick={()=>openMove(m)} className="hoverrow" style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, padding:'10px 10px', borderRadius:9, cursor:'pointer' }}>
      <div style={{ minWidth:0 }}><div style={{ fontSize:13.5, fontWeight:600, color:'var(--ink)' }}>{m.prenom} {m.nom}</div>
        <div style={{ fontSize:12, color:'var(--muted)' }}>{iFmtDate(m.date)} · {m.dep.ville||'?'} → {m.arr.ville||'?'}</div></div>
      <div style={{ textAlign:'right', flexShrink:0 }}>{right}</div>
    </div>
  );

  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'flex', alignItems:'flex-start', justifyContent:'space-between', gap:12, flexWrap:'wrap' }}>
        <div><h1 className="page-title">{greet} 👋</h1><p style={{ fontSize:13.5, color:'var(--muted)', margin:'6px 0 0', textTransform:'capitalize' }}>{iFmtLong(today)}</p></div>
        <button className="btn btn-primary" onClick={()=>openMove(iNewMove())}><Icon name="plus" size={16} />Nouveau déménagement</button>
      </div>

      {alertes.length>0 && <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
        {alertes.slice(0,6).map((a,i)=>{ const red=a.t==='red'; const col=red?'#C2362B':'#C77A0A'; return (
          <div key={i} onClick={()=>{ if(a.m) openMove(a.m); else if(a.inc) editIncident&&editIncident(a.inc); else if(a.nav) go(a.nav); }} style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 16px', borderRadius:11, cursor:'pointer', background:col+'1a', borderLeft:'4px solid '+col }}>
            <span style={{ color:col, display:'flex' }}><Icon name={a.inc?'shield':a.nav?'settings':'pin'} size={18} /></span>
            <div style={{ flex:1 }}><div style={{ fontWeight:600, fontSize:13.5, color:'var(--ink)' }}>{a.x}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{a.s}</div></div>
            <button className="btn btn-ghost btn-sm">Ouvrir</button>
          </div>); })}
      </div>}

      <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit,minmax(150px,1fr))', gap:12 }}>
        {kpis.map((k,i)=>(
          <div key={i} className="card card-pad" style={{ padding:'14px 16px' }}>
            <div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.06em', textTransform:'uppercase', color:'var(--muted)' }}>{k.label}</div>
            <div style={{ fontSize:24, fontWeight:800, marginTop:6, color: k.tone==='g'?'#16965A':k.tone==='r'?'#C2362B':k.tone==='b'?'#2563EB':'var(--ink)' }}>{k.value}</div>
          </div>))}
      </div>

      {window.LBC_ADMIN && (()=>{ const A=window.LBC_ADMIN, ag=A.adminAgg(), fa=A.financeAgg();
        const net=[ {l:'Partenaires actifs',v:ag.active}, {l:'Leads réseau (mois)',v:ag.leadsMonth}, {l:'Commissions à verser',v:euro(ag.pending),c:'#C77A0A'}, {l:'Revenu stockage',v:euro(fa.storageMrr)+' /m',c:'#2563EB'} ];
        return (<div>
          <div style={{ fontSize:11, fontWeight:700, letterSpacing:'.06em', textTransform:'uppercase', color:'var(--muted)', margin:'2px 0 10px' }}>🤝 Réseau partenaires</div>
          <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:12 }}>{net.map((k,i)=>(
            <div key={i} onClick={()=>go('partners')} className="card card-pad" style={{ padding:'14px 16px', cursor:'pointer' }}>
              <div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.06em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div>
              <div className="tnum" style={{ fontSize:22, fontWeight:800, marginTop:6, color:k.c||'var(--ink)' }}>{k.v}</div></div>))}</div>
        </div>); })()}

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
        <Card title="📦 Déménagements à venir">
          {aVenir.length===0 ? <IEmpty>Aucun déménagement confirmé dans les 30 prochains jours.</IEmpty> :
            aVenir.map(m=><Row key={m.id} m={m} right={<><div style={{ fontSize:12, fontWeight:700, color:'var(--ink)' }}>{m.heure}</div><div className="tnum" style={{ fontSize:12, color:'var(--muted)' }}>{euro(iNum(m.montant))}</div></>} />)}
        </Card>
        <Card title="🎯 Objectifs du mois">
          {objMois.length===0 ? <IEmpty>Aucun objectif. Ajoutez-en dans To-do & Objectifs.</IEmpty> :
            objMois.map(o=>{ const cur=o.type==='ca'?db.moves.filter(m=>iMonthKey(m.date)===mk).reduce((a,m)=>a+iCaEncaisse(m),0):mois.filter(m=>m.statut==='termine').length;
              const pct=Math.min(100,Math.round(cur/(o.cible||1)*100)); return (
              <div key={o.id} style={{ padding:'10px' }}>
                <div style={{ display:'flex', justifyContent:'space-between', fontSize:13, marginBottom:6 }}><span style={{ fontWeight:600, color:'var(--ink)' }}>{o.libelle}</span><span className="tnum" style={{ color:'var(--muted)' }}>{o.type==='ca'?euro(cur):cur} / {o.type==='ca'?euro(o.cible):o.cible}</span></div>
                <div style={{ height:7, background:'var(--bg)', borderRadius:999, overflow:'hidden' }}><div style={{ width:pct+'%', height:'100%', background:pct>=100?'#16965A':'var(--brand)', borderRadius:999 }} /></div>
              </div>); })}
        </Card>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:16 }}>
        <Card title="📨 Devis en attente de réponse">
          {devisAttente.length===0 ? <IEmpty>Aucun devis en attente</IEmpty> : devisAttente.map(m=><Row key={m.id} m={m} right={<span className="tnum" style={{ fontSize:13, fontWeight:600 }}>{euro(iNum(m.montant))}</span>} />)}
        </Card>
        <Card title="💰 Acomptes">
          {acomptes.length===0 ? <IEmpty>Aucun acompte en cours</IEmpty> : acomptes.map(m=><Row key={m.id} m={m} right={<span className="tnum" style={{ fontSize:13, fontWeight:600, color:'#C77A0A' }}>{euro(iNum(m.acompte))}</span>} />)}
        </Card>
        <Card title="💶 Paiements en attente" accent="#C77A0A">
          {paiements.length===0 ? <IEmpty>Aucun paiement en attente</IEmpty> : paiements.map(m=><Row key={m.id} m={m} right={<span className="tnum" style={{ fontSize:13, fontWeight:600 }}>{euro(iNum(m.montant)-iNum(m.acompte))}</span>} />)}
        </Card>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:16 }}>
        <Card title="📝 Devis à envoyer" count={devisAEnvoyer.length}>
          {devisAEnvoyer.length===0 ? <IEmpty>Aucun devis à envoyer</IEmpty> : devisAEnvoyer.map(m=>(
            <div key={m.id} style={{ padding:'10px' }}>
              <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', gap:8 }}>
                <div onClick={()=>openMove(m)} style={{ cursor:'pointer', minWidth:0 }}><div style={{ fontSize:13.5, fontWeight:600, color:'var(--ink)' }}>{m.prenom} {m.nom}</div>
                  <div style={{ fontSize:12, color:'var(--muted)' }}>{iFmtDate(m.date)} à {m.heure} · {m.dep.ville||'Nice'} → {m.arr.ville||'?'}</div></div>
                <span className="tnum" style={{ fontSize:13, fontWeight:700, color:'#2563EB' }}>{euro(iNum(m.montant))}</span>
              </div>
              <div style={{ display:'flex', gap:8, marginTop:8 }}>
                <button className="btn btn-ghost btn-sm" onClick={()=>window.LBC_INT.openDocGlobal(m,'devis')}><Icon name="fileText" size={13} />Devis</button>
                <button className="btn btn-primary btn-sm" onClick={()=>iMarkDevisEnvoye(m.id, showToast)}>📨 Envoyé</button>
              </div>
            </div>))}
        </Card>
        <Card title="⚡ Tâches urgentes" count={(taches.length+maintRetard.length)||null} accent="#B63D24">
          {(taches.length+maintRetard.length)===0 ? <IEmpty>Aucune tâche urgente</IEmpty> : <>
            {maintRetard.map((a,i)=>(
              <div key={'m'+i} style={{ display:'flex', alignItems:'flex-start', gap:10, padding:'10px', borderRadius:9 }}>
                <input type="checkbox" checked={false} title="Marquer l'entretien fait" onChange={()=>{ window.LBC_INT.validateEntretien(a.camId,a.type); showToast&&showToast('✅ Entretien validé — mis à jour dans Opérationnel'); }} style={{ marginTop:2 }} />
                <div style={{ minWidth:0 }}><div style={{ fontSize:13.5, color:'var(--ink)' }}><span style={{ fontSize:9.5, fontWeight:700, color:'#C77A0A', marginRight:6 }}>ENTRETIEN</span>🔧 {a.type} — {a.camNom}</div><div style={{ fontSize:11.5, color:'#C2362B', marginTop:2 }}>{a.label}</div></div>
              </div>))}
            {taches.map(t=>(
            <div key={t.id} onClick={()=>go('i_todo')} style={{ display:'flex', alignItems:'center', gap:10, padding:'10px', borderRadius:9, cursor:'pointer' }}>
              <span style={{ width:16, height:16, borderRadius:'50%', border:'2px solid #B63D24', flexShrink:0 }} />
              <div style={{ minWidth:0 }}><div style={{ fontSize:13.5, color:'var(--ink)' }}>{t.auto && <span style={{ fontSize:9.5, fontWeight:700, color:'#2563EB', marginRight:6 }}>AUTO</span>}📄 {t.title}</div>
                {t.due && <div style={{ fontSize:11.5, color:'#C77A0A', marginTop:2 }}>🔔 {iFmtDate(t.due)}</div>}</div>
            </div>))}
          </>}
        </Card>
        <Card title="⚠️ Litiges à régler" count={incidents.length||null} accent="#C2362B">
          {incidents.length===0 ? <div style={{ textAlign:'center', color:'#16965A', fontSize:13, padding:'28px 12px' }}>✓ Aucun litige en cours</div> :
            incidents.map(i=>(<div key={i.id} onClick={()=>editIncident&&editIncident(i)} className="hoverrow" style={{ padding:'10px', borderRadius:9, cursor:'pointer', display:'flex', justifyContent:'space-between', alignItems:'center', gap:10 }}>
              <div style={{ minWidth:0 }}><div style={{ fontSize:13.5, fontWeight:600, color:'var(--ink)' }}>{i.titre}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{i.client} · {iFmtDate(i.date)}</div></div>
              <span style={{ fontSize:11, fontWeight:700, color:I_INC_COLORS[i.statut]||'#C2362B', background:(I_INC_COLORS[i.statut]||'#C2362B')+'1f', padding:'3px 9px', borderRadius:999, whiteSpace:'nowrap' }}>{I_INC_STATUTS[i.statut]||'—'}</span>
            </div>))}
        </Card>
      </div>
    </div>
  );
}

/* ============================================================ DÉMÉNAGEMENTS */
function IDemenagements({ openMove, showToast, openIncident }){
  const db=useInt(); const [mois,setMois]=uIS('tous'); const [q,setQ]=uIS('');
  const moisOpts=Array.from(new Set(db.moves.map(m=>iMonthKey(m.date)).filter(Boolean))).sort().reverse();
  let list=db.moves.slice();
  if(mois!=='tous') list=list.filter(m=>iMonthKey(m.date)===mois);
  if(q) list=list.filter(m=>`${m.prenom} ${m.nom} ${m.dep.ville} ${m.arr.ville} ${m.id}`.toLowerCase().includes(q.toLowerCase()));
  list.sort((a,b)=>(b.date||'').localeCompare(a.date||''));
  const enc=list.reduce((a,m)=>a+iCaEncaisse(m),0);
  const benef=list.reduce((a,m)=>a+iBenefice(m),0);
  const acDus=list.filter(m=>iNum(m.acompte)>0&&!m.acompteRecu&&!['refuse','annule'].includes(m.statut)).reduce((a,m)=>a+iNum(m.acompte),0);
  const reste=list.filter(m=>['confirme','attente_paiement'].includes(m.statut)).reduce((a,m)=>a+(iNum(m.montant)-iCaEncaisse(m)),0);
  const kpis=[ {l:'Déménagements',v:String(list.length)}, {l:'CA encaissé',v:euro(enc),t:'g'}, {l:'Bénéfice net',v:euro(benef),t:'g'}, {l:'Acomptes à recevoir',v:acDus>0?euro(acDus):'—',t:'a'}, {l:'Reste à encaisser',v:reste>0?euro(reste):'—'} ];
  const setStatut=(m,s)=>{ const was=m.statut; iCommit(db=>{ const i=db.moves.findIndex(x=>x.id===m.id); if(i>=0){ db.moves[i].statut=s; iStampStatut(db.moves[i], s); } });
    if(s==='termine'&&was!=='termine'){ iCelebrate(m.montant); return; }
    if(s==='devis_envoye'&&was!=='devis_envoye'&&m.email&&window.LBC_SB&&window.LBC_SB.inviteClient){ window.LBC_SB.inviteClient(m.email).then(r=>showToast(r&&r.ok?('Devis envoyé · invitation envoyée à '+m.email):'Devis envoyé (invitation non partie)')); return; }
    showToast('Statut → '+I_STATUS[s].label);
  };

  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, flexWrap:'wrap' }}>
        <div style={{ position:'relative', flex:1, maxWidth:280 }}>
          <span style={{ position:'absolute', left:12, top:11, color:'var(--muted)' }}><Icon name="search" size={16} /></span>
          <IInput value={q} onChange={e=>setQ(e.target.value)} placeholder="Client, ville, n°…" style={{ paddingLeft:36 }} />
        </div>
        <div style={{ display:'flex', gap:10 }}>
          <ISelect value={mois} onChange={e=>setMois(e.target.value)} style={{ width:'auto' }}><option value="tous">Tous les mois</option>{moisOpts.map(k=><option key={k} value={k} style={{ textTransform:'capitalize' }}>{iMonthLabel(k)}</option>)}</ISelect>
          <button className="btn btn-ghost" title="Simule la réception d'un formulaire de devis depuis ton site web" onClick={()=>{ const s=iSampleWebLead(); window.LBC_INT.ingestWebLead(s); showToast('🌐 Lead du site reçu — '+s.client.prenom+' '+s.client.nom+' ('+(s.formulaireType==='detaille'?'détaillé':'basique')+')'); }}><Icon name="download" size={16} />Simuler lead site</button>
          <button className="btn btn-primary" onClick={()=>openMove(iNewMove())}><Icon name="plus" size={16} />Nouveau déménagement</button>
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'repeat(5,1fr)', gap:14 }}>
        {kpis.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'14px 16px' }}>
          <div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.06em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div>
          <div className="tnum" style={{ fontSize:22, fontWeight:800, marginTop:6, color:k.t==='g'?'#16965A':k.t==='a'?'#C77A0A':'var(--ink)' }}>{k.v}</div></div>))}
      </div>

      {list.length===0 ? <div className="card card-pad"><IEmpty pad={40}>Aucun déménagement. Créez-en un avec « Nouveau déménagement ».</IEmpty></div> :
        <div style={{ display:'flex', flexDirection:'column', gap:12 }}>
          {list.map(m=>(
            <div key={m.id} className="card" style={{ padding:'16px 18px', borderLeft:m.statut==='termine'?'3px solid #16965A':(m.statut==='refuse'||m.statut==='annule'?'3px solid #C2362B':'none'), background:m.statut==='termine'?'rgba(22,150,90,.035)':'var(--card)' }}>
              <div style={{ display:'flex', alignItems:'flex-start', gap:18, flexWrap:'wrap' }}>
                <div style={{ minWidth:150 }}><div style={{ fontSize:11, fontWeight:700, color:'var(--faint)', letterSpacing:'.05em' }}>{m.id}</div>
                  <div style={{ marginTop:5 }}><IPill s={m.statut} /></div></div>
                <div style={{ minWidth:140 }}><div style={{ fontSize:10, fontWeight:700, color:'var(--faint)', textTransform:'uppercase' }}>Client</div>
                  <div style={{ fontSize:14.5, fontWeight:700, color:'var(--ink)', marginTop:3 }}>{m.prenom} {m.nom}</div>
                  <div style={{ fontSize:12, color:'var(--muted)' }}>{m.tel||I_SOURCES[m.source]||''}</div>
                  {m.partenaire && <div style={{ fontSize:11, fontWeight:600, color:'#B63D24', marginTop:2, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', maxWidth:140 }}>🤝 {m.partenaire}</div>}</div>
                <div style={{ minWidth:110 }}><div style={{ fontSize:10, fontWeight:700, color:'var(--faint)', textTransform:'uppercase' }}>Date</div>
                  <div style={{ fontSize:13.5, fontWeight:600, color:'var(--ink)', marginTop:3 }}>{iFmtDate(m.date)}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{m.heure}</div></div>
                <div style={{ flex:1, minWidth:140 }}><div style={{ fontSize:10, fontWeight:700, color:'var(--faint)', textTransform:'uppercase' }}>Trajet</div>
                  <div style={{ marginTop:3 }}><Route from={m.dep.ville||'?'} to={m.arr.ville||'?'} /></div></div>
                <div style={{ minWidth:80 }}><div style={{ fontSize:10, fontWeight:700, color:'var(--faint)', textTransform:'uppercase' }}>Formule</div><div style={{ marginTop:3 }}><IFormule f={m.formule} /></div></div>
                <div style={{ minWidth:70, textAlign:'right' }}><div style={{ fontSize:10, fontWeight:700, color:'var(--faint)', textTransform:'uppercase' }}>CA</div><div className="tnum" style={{ fontSize:14, fontWeight:700, color:'var(--ink)', marginTop:3 }}>{m.montant?euro(iNum(m.montant)):'—'}</div></div>
                <div style={{ minWidth:70, textAlign:'right' }}><div style={{ fontSize:10, fontWeight:700, color:'var(--faint)', textTransform:'uppercase' }}>Bénéf.</div><div className="tnum" style={{ fontSize:14, fontWeight:700, color:'#16965A', marginTop:3 }}>{m.statut==='termine'?euro(iBenefice(m)):'—'}</div></div>
              </div>
              <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, marginTop:14, paddingTop:14, borderTop:'1px solid var(--border)', flexWrap:'wrap' }}>
                <div style={{ display:'flex', gap:7, flexWrap:'wrap', alignItems:'center' }}>
                  {iPrevStatut(m.statut) && <button className="btn btn-ghost btn-sm" onClick={()=>setStatut(m,iPrevStatut(m.statut))} title="Étape précédente"><Icon name="arrowR" size={14} style={{ transform:'scaleX(-1)' }} />Retour</button>}
                  {iNextStatut(m.statut)
                    ? <button className="btn btn-primary btn-sm" onClick={()=>setStatut(m,iNextStatut(m.statut))}>{I_STATUS[iNextStatut(m.statut)].label}<Icon name="arrowR" size={14} /></button>
                    : (m.statut==='termine' ? <span style={{ display:'inline-flex', alignItems:'center', gap:6, fontSize:13, fontWeight:700, color:'#16965A' }}>🏆 Mission terminée</span> : null)}
                  {m.statut==='termine' && <button className="btn btn-ghost btn-sm" onClick={()=>setStatut(m,'attente_paiement')} title="Rouvrir la mission"><Icon name="arrowR" size={14} style={{ transform:'scaleX(-1)' }} />Réouvrir</button>}
                  {['refuse','annule'].includes(m.statut) && <button className="btn btn-ghost btn-sm" onClick={()=>setStatut(m,'devis_a_envoyer')} title="Remettre dans le pipeline"><Icon name="arrowR" size={14} style={{ transform:'scaleX(-1)' }} />Réouvrir</button>}
                  {!['refuse','annule','termine'].includes(m.statut) && <button className="btn btn-ghost btn-sm" onClick={()=>setStatut(m,'refuse')} style={{ color:'#C2362B' }}>✕ Refuser</button>}
                </div>
                <div style={{ display:'flex', gap:7, flexWrap:'wrap' }}>
                  <button className="btn btn-ghost btn-sm" onClick={()=>window.LBC_INT.openDocGlobal(m,'devis')}><Icon name="fileText" size={14} />Devis</button>
                  {m.statut==='termine' && <button className="btn btn-ghost btn-sm" onClick={()=>window.LBC_INT.openDocGlobal(m,'facture')}><Icon name="receipt" size={14} />Facture</button>}
                  <button className="btn btn-ghost btn-sm" onClick={()=>openMove(m)}><Icon name="settings" size={14} />Modifier</button>
                  <button className="btn btn-ghost btn-sm" onClick={()=>openIncident(m)} style={{ color:'#C77A0A' }}><Icon name="shield" size={14} />Incident</button>
                  <button className="btn btn-ghost btn-sm" onClick={()=>{ if(confirm('Supprimer ce déménagement ?')){ iCommit(db=>{ db.moves=db.moves.filter(x=>x.id!==m.id); }); showToast('Supprimé'); } }} style={{ color:'var(--perdu)' }}><Icon name="x" size={14} /></button>
                </div>
              </div>
            </div>))}
        </div>}
    </div>
  );
}

/* ============================================================ CALENDRIER */
function ICalendrier({ openMove, go, showToast }){
  const db=useInt(); const now=new Date(); const [ref,setRef]=uIS({ y:now.getFullYear(), m:now.getMonth() }); const today=iToday();
  const [eventEdit,setEventEdit]=uIS(null);
  const evs={};
  db.moves.forEach(m=>{ if(m.date&&!['refuse','annule'].includes(m.statut)) (evs[m.date]=evs[m.date]||[]).push({ m, type:'dem' });
    if(m.rdvDate) (evs[m.rdvDate]=evs[m.rdvDate]||[]).push({ m, type:'rdv' }); });
  (db.tasks||[]).forEach(t=>{ if(t.due && !t.done) (evs[t.due]=evs[t.due]||[]).push({ task:t, type:t.kind==='event'?'event':'task' }); });
  const first=new Date(ref.y,ref.m,1); let dow=first.getDay(); dow=dow===0?6:dow-1;
  const last=new Date(ref.y,ref.m+1,0).getDate(); const cells=[];
  for(let i=0;i<dow;i++) cells.push(null); for(let d=1;d<=last;d++) cells.push(d); while(cells.length%7) cells.push(null);
  const prev=()=>setRef(c=>c.m===0?{y:c.y-1,m:11}:{y:c.y,m:c.m-1});
  const next=()=>setRef(c=>c.m===11?{y:c.y+1,m:0}:{y:c.y,m:c.m+1});
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', flexWrap:'wrap', gap:10 }}>
        <div style={{ display:'flex', gap:14, fontSize:12.5, flexWrap:'wrap' }}>
          <span style={{ display:'inline-flex', alignItems:'center', gap:7 }}><span style={{ width:9, height:9, borderRadius:'50%', background:'var(--brand)' }} />Déménagement</span>
          <span style={{ display:'inline-flex', alignItems:'center', gap:7 }}><span style={{ width:9, height:9, borderRadius:'50%', background:'#2563EB' }} />RDV devis</span>
          <span style={{ display:'inline-flex', alignItems:'center', gap:7 }}><span style={{ width:9, height:9, borderRadius:'50%', background:'#16965A' }} />Tâche</span>
          <span style={{ display:'inline-flex', alignItems:'center', gap:7 }}><span style={{ width:9, height:9, borderRadius:'50%', background:'#6D4AC4' }} />Événement</span>
        </div>
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          <button className="btn btn-ghost btn-sm" onClick={()=>setEventEdit(iNewEvent())} title="Ajouter un événement">📅 Événement</button>
          <button className="btn btn-ghost btn-sm" onClick={prev}><Icon name="chevronL" size={16} /></button>
          <span style={{ fontWeight:700, minWidth:140, textAlign:'center', textTransform:'capitalize' }}>{I_MONTHS[ref.m]} {ref.y}</span>
          <button className="btn btn-ghost btn-sm" onClick={next}><Icon name="chevronR" size={16} /></button>
        </div>
      </div>
      <div className="card card-pad">
        <div style={{ display:'grid', gridTemplateColumns:'repeat(7,1fr)', gap:6 }}>
          {['Lun','Mar','Mer','Jeu','Ven','Sam','Dim'].map(d=><div key={d} style={{ textAlign:'center', fontSize:11, fontWeight:700, color:'var(--muted)', padding:'4px 0' }}>{d}</div>)}
          {cells.map((d,i)=>{ if(!d) return <div key={i} />;
            const ds=ref.y+'-'+String(ref.m+1).padStart(2,'0')+'-'+String(d).padStart(2,'0'); const list=evs[ds]||[];
            return (<div key={i} style={{ minHeight:84, border:'1px solid var(--border)', borderRadius:9, padding:6, background:ds===today?'rgba(182,61,36,.05)':'var(--card)' }}>
              <div style={{ fontSize:11.5, fontWeight:ds===today?800:500, color:ds===today?'var(--brand)':'var(--muted)', marginBottom:4 }}>{d}</div>
              {list.slice(0,3).map((e,k)=>(<div key={k} onClick={()=> e.type==='event' ? setEventEdit(e.task) : e.type==='task' ? (go&&go('i_todo')) : openMove(e.m)} style={{ fontSize:10.5, fontWeight:600, padding:'2px 6px', borderRadius:5, marginBottom:3, cursor:'pointer', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis',
                background:e.type==='dem'?'rgba(182,61,36,.12)':e.type==='task'?'rgba(22,150,90,.14)':e.type==='event'?'rgba(109,74,196,.14)':'rgba(37,99,235,.12)', color:e.type==='dem'?'var(--brand)':e.type==='task'?'#16965A':e.type==='event'?'#6D4AC4':'#2563EB' }}>{e.type==='event'?('📌 '+(e.task.heure?e.task.heure+' ':'')+e.task.title):e.type==='task'?('• '+e.task.title):(e.type==='rdv'?'◷ ':'')+e.m.prenom+' '+(e.m.nom||'')[0]+'.'}</div>))}
              {list.length>3 && <div style={{ fontSize:10, color:'var(--faint)' }}>+{list.length-3}</div>}
            </div>); })}
        </div>
      </div>
      {eventEdit && <IEventDrawer event={eventEdit} onClose={()=>setEventEdit(null)} showToast={showToast||(()=>{})} />}
    </div>
  );
}

/* ============================================================ TO-DO & OBJECTIFS */
function ITaskDrawer({ task, onClose, showToast }){
  const db=useInt(); const isNew=!db.tasks.some(x=>x.id===task.id);
  const [t,setT]=uIS(()=>JSON.parse(JSON.stringify(task)));
  const f=(k,v)=>setT(s=>({...s,[k]:v}));
  const addSub=()=>setT(s=>({...s,subtasks:[...s.subtasks,{ id:iUid('S'), title:'', done:false }]}));
  const setSub=(id,k,v)=>setT(s=>({...s,subtasks:s.subtasks.map(x=>x.id===id?{...x,[k]:v}:x)}));
  const delSub=(id)=>setT(s=>({...s,subtasks:s.subtasks.filter(x=>x.id!==id)}));
  const save=()=>{ if(!t.title.trim()){ showToast('Titre requis'); return; } iCommit(db=>{ const i=db.tasks.findIndex(x=>x.id===t.id); if(i>=0) db.tasks[i]=t; else db.tasks.unshift(t); }); showToast(isNew?'Tâche créée':'Tâche enregistrée'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.tasks=db.tasks.filter(x=>x.id!==t.id); }); showToast('Tâche supprimée'); onClose(); };
  return (
    <IDrawer title={isNew?'Nouvelle tâche':'Modifier la tâche'} onClose={onClose} width={480}
      footer={<>{!isNew?<button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}><Icon name="x" size={14} />Supprimer</button>:<span />}
        <div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>Enregistrer</button></div></>}>
      <IField label="Tâche"><IInput value={t.title} onChange={e=>f('title',e.target.value)} placeholder="Ex : Relancer le client…" /></IField>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:14 }}>
        <IField label="Quadrant (Eisenhower)"><ISelect value={t.quad} onChange={e=>f('quad',e.target.value)}>{Object.values(I_QUADRANTS).map(qd=><option key={qd.key} value={qd.key}>{qd.title}</option>)}</ISelect></IField>
        <IField label="Échéance"><IInput type="date" value={t.due} onChange={e=>f('due',e.target.value)} /></IField>
      </div>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', margin:'20px 0 8px' }}>
        <span style={{ fontSize:13, fontWeight:700, color:'var(--ink)' }}>Sous-tâches</span>
        <button className="btn btn-ghost btn-sm" onClick={addSub}><Icon name="plus" size={13} />Ajouter</button></div>
      {t.subtasks.length===0 ? <IEmpty pad={14}>Aucune sous-tâche</IEmpty> : t.subtasks.map(s=>(
        <div key={s.id} style={{ display:'flex', alignItems:'center', gap:9, marginBottom:8 }}>
          <input type="checkbox" checked={s.done} onChange={e=>setSub(s.id,'done',e.target.checked)} />
          <IInput value={s.title} onChange={e=>setSub(s.id,'title',e.target.value)} placeholder="Sous-tâche…" style={{ flex:1, height:34 }} />
          <button onClick={()=>delSub(s.id)} style={{ color:'var(--muted)', display:'flex', padding:4 }}><Icon name="x" size={15} /></button>
        </div>))}
    </IDrawer>
  );
}
function IEventDrawer({ event, onClose, showToast }){
  const db=useInt(); const isNew=!db.tasks.some(x=>x.id===event.id);
  const [e,setE]=uIS(()=>JSON.parse(JSON.stringify(event)));
  const f=(k,v)=>setE(s=>({...s,[k]:v}));
  const save=()=>{ if(!e.title.trim()){ showToast('Titre de l\'événement requis'); return; } if(!e.due){ showToast('Choisis une date'); return; } iCommit(db=>{ const i=db.tasks.findIndex(x=>x.id===e.id); if(i>=0) db.tasks[i]=e; else db.tasks.unshift(e); }); showToast(isNew?'📅 Événement ajouté au calendrier':'Événement enregistré'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.tasks=db.tasks.filter(x=>x.id!==e.id); }); showToast('Événement supprimé'); onClose(); };
  return (
    <IDrawer title={isNew?'Nouvel événement':'Modifier l\'événement'} onClose={onClose} width={460}
      footer={<>{!isNew?<button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}><Icon name="x" size={14} />Supprimer</button>:<span />}
        <div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>Enregistrer</button></div></>}>
      <IField label="Événement"><IInput value={e.title} onChange={ev=>f('title',ev.target.value)} placeholder="Ex : Visite chantier, RDV client, rappel…" /></IField>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:14 }}>
        <IField label="Date"><IInput type="date" value={e.due} onChange={ev=>f('due',ev.target.value)} /></IField>
        <IField label="Heure (optionnel)"><IInput type="time" value={e.heure} onChange={ev=>f('heure',ev.target.value)} /></IField>
      </div>
      <div style={{ marginTop:14 }}><IField label="Lieu (optionnel)"><IInput value={e.lieu} onChange={ev=>f('lieu',ev.target.value)} placeholder="Adresse, ville…" /></IField></div>
      <p style={{ fontSize:12, color:'var(--muted)', marginTop:12, lineHeight:1.5 }}>L'événement apparaîtra dans ton <b>calendrier</b> à la date choisie, distinct des déménagements et des tâches.</p>
    </IDrawer>
  );
}
function IObjDrawer({ obj, onClose, showToast }){
  const db=useInt(); const isNew=!db.objectifs.some(x=>x.id===obj.id);
  const [o,setO]=uIS(()=>JSON.parse(JSON.stringify(obj))); const f=(k,v)=>setO(s=>({...s,[k]:v}));
  const save=()=>{ iCommit(db=>{ const i=db.objectifs.findIndex(x=>x.id===o.id); if(i>=0) db.objectifs[i]=o; else db.objectifs.push(o); }); showToast('Objectif enregistré'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.objectifs=db.objectifs.filter(x=>x.id!==o.id); }); showToast('Objectif supprimé'); onClose(); };
  return (
    <IDrawer title={isNew?'Nouvel objectif':'Modifier l\'objectif'} onClose={onClose} width={440}
      footer={<>{!isNew?<button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}>Supprimer</button>:<span />}<div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>Enregistrer</button></div></>}>
      <IField label="Libellé"><IInput value={o.libelle} onChange={e=>f('libelle',e.target.value)} placeholder="Ex : CA mensuel" /></IField>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:14 }}>
        <IField label="Type"><ISelect value={o.type} onChange={e=>f('type',e.target.value)}><option value="ca">Chiffre d'affaires (€)</option><option value="moves">Déménagements</option></ISelect></IField>
        <IField label="Cible"><IInput type="number" value={o.cible} onChange={e=>f('cible',iNum(e.target.value))} /></IField>
      </div>
      <div style={{ marginTop:14 }}><IField label="Mois"><IInput type="month" value={o.mois} onChange={e=>f('mois',e.target.value)} /></IField></div>
    </IDrawer>
  );
}
function ITodo({ showToast, go }){
  const db=useInt(); const [q,setQ]=uIS(''); const [showDone,setShowDone]=uIS(false);
  const [taskEdit,setTaskEdit]=uIS(null); const [objEdit,setObjEdit]=uIS(null); const [eventEdit,setEventEdit]=uIS(null);
  const today=iToday();
  const matches=(s)=>`${s}`.toLowerCase().includes(q.toLowerCase());
  const all=db.tasks.filter(t=>t.kind!=='event' && matches(t.title));
  const active=all.filter(t=>!t.done); const done=all.filter(t=>t.done);
  const evenements=db.tasks.filter(t=>t.kind==='event' && t.due>=today && matches(t.title)).sort((a,b)=>(a.due+' '+(a.heure||'')).localeCompare(b.due+' '+(b.heure||'')));
  // tâches AUTO : un "Envoyer le devis" par déménagement en devis à envoyer
  const autoTasks=db.moves.filter(m=>m.statut==='devis_a_envoyer' && `envoyer le devis ${m.prenom} ${m.nom}`.toLowerCase().includes(q.toLowerCase())).map(m=>({ id:'auto-'+m.id, _moveId:m.id, title:'Envoyer le devis — '+((m.prenom||'')+' '+(m.nom||'')).trim(), quad:'now', done:false, due:m.date||m.createdAt, auto:true, subtasks:[] }));
  // tâches AUTO : entretiens dus + stock bas (cliquables vers Opérationnel)
  const autoMaint=iToutesAlertesFlotte().map(a=>({ id:'maint-'+a.camId+'-'+a.type, _maint:{ camId:a.camId, type:a.type }, _nav:'i_operationnel', title:'Entretien : '+a.type+' — '+a.camNom+' ('+a.label+')', quad:a.statut==='retard'?'now':'plan', done:false, due:a.dueDate||'', auto:true, subtasks:[] })).filter(t=>matches(t.title));
  const autoStock=iStockBas().map(m=>({ id:'stock-'+m.id, _nav:'i_operationnel', title:'Renflouer le stock : '+m.nom+' ('+m.unites+' restant)', quad:'deleg', done:false, due:'', auto:true, subtasks:[] })).filter(t=>matches(t.title));
  const autoLitiges=(db.incidents||[]).filter(iIncidentOuvert).map(i=>({ id:'litige-'+i.id, _nav:'i_accueil', title:'Litige à régler : '+i.titre+(i.client?' — '+i.client:''), quad:'now', done:false, due:'', auto:true, subtasks:[] })).filter(t=>matches(t.title));
  // tâches AUTO : relances devis (sans réponse) et paiement (en attente), avec bouton d'envoi direct
  const nomMove=m=>((m.prenom||'')+' '+(m.nom||'')).trim()||'client';
  const ordinal=n=>n===0?'1re':(n+1)+'e';
  const autoRelDevis=db.moves.map(iRelanceDevisDue).filter(Boolean).map(x=>({ id:'reldevis-'+x.move.id, _relance:{ moveId:x.move.id, kind:'devis', n:x.n }, title:'Relancer le devis — '+nomMove(x.move)+' ('+ordinal(x.n)+' relance)', quad:'deleg', done:false, due:'', auto:true, subtasks:[] })).filter(t=>matches(t.title));
  const autoRelPaie=db.moves.map(iRelancePaieDue).filter(Boolean).map(x=>({ id:'relpaie-'+x.move.id, _relance:{ moveId:x.move.id, kind:'paie', n:x.n }, title:'Relancer le paiement — '+nomMove(x.move)+' ('+euro(iNum(x.move.montant)-iCaEncaisse(x.move))+' dû)', quad:'now', done:false, due:'', auto:true, subtasks:[] })).filter(t=>matches(t.title));
  const late=active.filter(t=>t.due&&t.due<today);
  const kpis=[ {l:'Faire now',v:active.filter(t=>t.quad==='now').length+autoTasks.length+autoMaint.filter(t=>t.quad==='now').length+autoLitiges.length+autoRelPaie.length,c:'#B63D24'}, {l:'Planifier',v:active.filter(t=>t.quad==='plan').length+autoMaint.filter(t=>t.quad==='plan').length,c:'#2563EB'}, {l:'Déléguer',v:active.filter(t=>t.quad==='deleg').length+autoStock.length+autoRelDevis.length,c:'#C77A0A'}, {l:'En retard',v:late.length,c:'#C2362B'}, {l:'Accomplies',v:done.length,c:'#16965A'} ];
  const toggle=(t)=>{ if(t._maint){ window.LBC_INT.validateEntretien(t._maint.camId, t._maint.type); showToast('✅ Entretien validé — mis à jour dans Opérationnel'); return; } if(t._nav){ go&&go(t._nav); return; } if(t._moveId){ iMarkDevisEnvoye(t._moveId, showToast); return; } iCommit(db=>{ const i=db.tasks.findIndex(x=>x.id===t.id); if(i>=0) db.tasks[i].done=!db.tasks[i].done; }); };
  const toggleSub=(t,sid)=>iCommit(db=>{ const i=db.tasks.findIndex(x=>x.id===t.id); if(i>=0){ const s=db.tasks[i].subtasks.find(x=>x.id===sid); if(s) s.done=!s.done; } });
  const subPct=(t)=> t.subtasks.length? Math.round(t.subtasks.filter(s=>s.done).length/t.subtasks.length*100):null;

  const TaskItem=({ t })=>{ const pct=subPct(t); return (
    <div style={{ borderBottom:'1px solid var(--border)', padding:'12px 4px' }}>
      <div style={{ display:'flex', alignItems:'flex-start', gap:11 }}>
        {t._maint ? <input type="checkbox" checked={false} onChange={()=>toggle(t)} title="Marquer l'entretien comme fait" style={{ marginTop:3 }} /> : (t._nav||t._relance) ? <span style={{ fontSize:15, marginTop:1 }}>{t._relance?'📧':String(t.id).indexOf('stock-')===0?'📦':String(t.id).indexOf('litige-')===0?'⚠️':'🔧'}</span> : <input type="checkbox" checked={t.done} onChange={()=>toggle(t)} style={{ marginTop:3 }} />}
        <div style={{ flex:1, minWidth:0, cursor:'pointer' }} onClick={()=> t._nav ? (go&&go(t._nav)) : (t._moveId||t._relance) ? (window.LBC_INT.openMoveGlobal && window.LBC_INT.openMoveGlobal(db.moves.find(x=>x.id===(t._moveId||t._relance.moveId)))) : setTaskEdit(t)}>
          <div style={{ fontSize:13.5, color:'var(--ink)', textDecoration:t.done?'line-through':'none', opacity:t.done?.6:1 }}>
            {t.auto && <span style={{ fontSize:9.5, fontWeight:700, color:t._relance?(t._relance.kind==='paie'?'#C2362B':'#C77A0A'):t._nav?(String(t.id).indexOf('litige-')===0?'#C2362B':'#C77A0A'):'#2563EB', marginRight:6 }}>{t._relance?'RELANCE':t._nav?(String(t.id).indexOf('stock-')===0?'STOCK':String(t.id).indexOf('litige-')===0?'LITIGE':'ENTRETIEN'):'AUTO'}</span>}{t.title}</div>
          {(t.due||pct!=null) && <div style={{ display:'flex', gap:10, fontSize:11.5, color:'var(--muted)', marginTop:3 }}>{t.due && <span style={{ color:t.due<today&&!t.done?'#C2362B':'var(--muted)' }}>{iFmtDate(t.due)}</span>}{pct!=null && <span>{pct}%</span>}</div>}
          {t.subtasks.length>0 && <div style={{ marginTop:8, display:'flex', flexDirection:'column', gap:5 }}>
            {t.subtasks.map(s=>(<label key={s.id} onClick={e=>e.stopPropagation()} style={{ display:'flex', alignItems:'center', gap:8, fontSize:12.5, color:'var(--ink-2)' }}>
              <input type="checkbox" checked={s.done} onChange={()=>toggleSub(t,s.id)} /><span style={{ textDecoration:s.done?'line-through':'none', opacity:s.done?.6:1 }}>{s.title}</span></label>))}
          </div>}
        </div>
        {t._relance && <button className="btn btn-primary btn-sm" onClick={(e)=>{ e.stopPropagation(); iSendRelance(db.moves.find(x=>x.id===t._relance.moveId), t._relance.kind, t._relance.n, showToast); }} style={{ flexShrink:0, alignSelf:'flex-start', whiteSpace:'nowrap' }} title="Envoyer l'email de relance directement au client">📧 Relancer</button>}
      </div>
    </div>); };

  const Quad=({ qd })=>{ const items = qd.key==='now' ? autoRelPaie.concat(autoLitiges, autoTasks, autoMaint.filter(t=>t.quad==='now'), active.filter(t=>t.quad==='now')) : qd.key==='plan' ? autoMaint.filter(t=>t.quad==='plan').concat(active.filter(t=>t.quad==='plan')) : qd.key==='deleg' ? autoRelDevis.concat(autoStock, active.filter(t=>t.quad==='deleg')) : active.filter(t=>t.quad===qd.key); return (
    <div className="card" style={{ overflow:'hidden', borderTop:'3px solid '+qd.c }}>
      <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
        <div><div style={{ fontSize:13.5, fontWeight:800, letterSpacing:'.04em', textTransform:'uppercase', color:qd.c }}>{qd.emoji} {qd.title}</div>
          <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:2 }}>{qd.sub}</div></div>
        <span style={{ fontSize:11.5, fontWeight:700, background:qd.c, color:'#fff', borderRadius:999, minWidth:20, textAlign:'center', padding:'1px 7px' }}>{items.length}</span>
      </div>
      <div style={{ padding:'4px 14px 10px' }}>{items.length===0 ? <IEmpty pad={18}>Aucune tâche</IEmpty> : items.map(t=><TaskItem key={t.id} t={t} />)}</div>
    </div>); };

  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', gap:12, flexWrap:'wrap' }}>
        <div style={{ position:'relative', flex:1, maxWidth:340 }}><span style={{ position:'absolute', left:12, top:11, color:'var(--muted)' }}><Icon name="search" size={16} /></span>
          <IInput value={q} onChange={e=>setQ(e.target.value)} placeholder="Rechercher une tâche…" style={{ paddingLeft:36 }} /></div>
        <div style={{ display:'flex', gap:8 }}>
          <button className="btn btn-ghost" onClick={()=>setEventEdit(iNewEvent())} title="Ajouter un événement au calendrier (RDV, visite…)">📅 Événement</button>
          <button className="btn btn-primary" onClick={()=>setTaskEdit(iNewTask('now'))}><Icon name="plus" size={16} />Nouvelle tâche</button>
        </div>
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(5,1fr)', gap:14 }}>
        {kpis.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'14px 16px' }}>
          <div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.06em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div>
          <div style={{ fontSize:24, fontWeight:800, marginTop:6, color:k.c }}>{k.v}</div></div>))}
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
        <Quad qd={I_QUADRANTS.now} /><Quad qd={I_QUADRANTS.plan} /><Quad qd={I_QUADRANTS.deleg} /><Quad qd={I_QUADRANTS.later} />
      </div>

      <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span className="section-title" style={{ fontSize:15 }}>📅 Événements à venir <span style={{ color:'var(--muted)', fontWeight:400 }}>{evenements.length||''}</span></span>
          <button className="btn btn-ghost btn-sm" onClick={()=>setEventEdit(iNewEvent())}><Icon name="plus" size={14} />Événement</button>
        </div>
        <div style={{ padding:'6px 14px 10px' }}>
          {evenements.length===0 ? <IEmpty pad={16}>Aucun événement à venir. Ajoute un RDV, une visite…</IEmpty> : evenements.map(ev=>(
            <div key={ev.id} onClick={()=>setEventEdit(ev)} style={{ display:'flex', alignItems:'center', gap:12, padding:'11px 4px', borderBottom:'1px solid var(--border)', cursor:'pointer' }}>
              <div style={{ flexShrink:0, width:46, textAlign:'center' }}>
                <div style={{ fontSize:17, fontWeight:800, color:'#6D4AC4', lineHeight:1 }}>{ev.due.slice(8,10)}</div>
                <div style={{ fontSize:10, color:'var(--muted)', textTransform:'uppercase' }}>{I_MONTHS_AB[parseInt(ev.due.slice(5,7))-1]}</div>
              </div>
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ fontSize:13.5, fontWeight:600, color:'var(--ink)' }}>{ev.title}</div>
                <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:2 }}>{[ev.heure?'🕐 '+ev.heure:'', ev.lieu?'📍 '+ev.lieu:''].filter(Boolean).join(' · ')||iFmtDate(ev.due)}</div>
              </div>
            </div>))}
        </div>
      </div>

      <div className="card" style={{ overflow:'hidden' }}>
        <div onClick={()=>setShowDone(s=>!s)} style={{ padding:'14px 18px', display:'flex', alignItems:'center', justifyContent:'space-between', cursor:'pointer' }}>
          <span style={{ fontSize:13.5, fontWeight:700, color:'#16965A' }}>✓ Accomplies <span style={{ color:'var(--muted)' }}>{done.length}</span></span>
          <Icon name={showDone?'chevronD':'chevronR'} size={16} style={{ color:'var(--muted)' }} />
        </div>
        {showDone && <div style={{ padding:'0 14px 10px' }}>{done.length===0?<IEmpty pad={14}>Rien d'accompli pour l'instant</IEmpty>:done.map(t=><TaskItem key={t.id} t={t} />)}</div>}
      </div>

      <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span className="section-title" style={{ fontSize:15 }}>🎯 Objectifs société</span>
          <button className="btn btn-ghost btn-sm" onClick={()=>setObjEdit({ id:iUid('O'), mois:iMonthKey(today), libelle:'', cible:0, type:'ca' })}><Icon name="plus" size={14} />Objectif</button>
        </div>
        <div style={{ padding:'10px 14px' }}>
          {db.objectifs.length===0 ? <IEmpty>Aucun objectif défini</IEmpty> : db.objectifs.map(o=>{
            const cur=o.type==='ca'?db.moves.filter(m=>iMonthKey(m.date)===o.mois).reduce((a,m)=>a+iCaEncaisse(m),0):db.moves.filter(m=>iMonthKey(m.date)===o.mois&&m.statut==='termine').length;
            const pct=Math.min(100,Math.round(cur/(o.cible||1)*100)); return (
            <div key={o.id} onClick={()=>setObjEdit(o)} style={{ padding:'10px 6px', cursor:'pointer', borderBottom:'1px solid var(--border)' }}>
              <div style={{ display:'flex', justifyContent:'space-between', fontSize:13, marginBottom:6 }}><span style={{ fontWeight:600, color:'var(--ink)' }}>{o.libelle} <span style={{ color:'var(--muted)', fontWeight:400, textTransform:'capitalize' }}>· {iMonthLabel(o.mois)}</span></span>
                <span className="tnum" style={{ color:'var(--muted)' }}>{o.type==='ca'?euro(cur):cur} / {o.type==='ca'?euro(o.cible):o.cible}</span></div>
              <div style={{ height:7, background:'var(--bg)', borderRadius:999, overflow:'hidden' }}><div style={{ width:pct+'%', height:'100%', background:pct>=100?'#16965A':'var(--brand)', borderRadius:999 }} /></div>
            </div>); })}
        </div>
      </div>

      {taskEdit && <ITaskDrawer task={taskEdit} onClose={()=>setTaskEdit(null)} showToast={showToast} />}
      {eventEdit && <IEventDrawer event={eventEdit} onClose={()=>setEventEdit(null)} showToast={showToast} />}
      {objEdit && <IObjDrawer obj={objEdit} onClose={()=>setObjEdit(null)} showToast={showToast} />}
    </div>
  );
}

/* ============================================================ CLIENTS */
function iBuildClients(moves){
  const eng=['confirme','attente_paiement','termine'];
  const map={};
  moves.filter(m=>eng.includes(m.statut)).forEach(m=>{ const k=(m.tel||(m.prenom+m.nom)).trim();
    if(!map[k]) map[k]={ prenom:m.prenom, nom:m.nom, tel:m.tel, email:m.email, source:m.source, moves:[], ca:0, avis:[] };
    map[k].moves.push(m); map[k].ca+=iCaEncaisse(m); if(m.avis) map[k].avis.push(m.avis); });
  return Object.values(map).map(c=>({ ...c, premium:c.moves.some(m=>m.formule==='premium'), fidele:c.moves.length>1 })).sort((a,b)=>b.ca-a.ca);
}
function IClientDetail({ client, onClose, openMove }){
  const db=useInt();
  const c=client; const full=((c.prenom||'')+' '+(c.nom||'')).trim();
  const boxes=(db.boxes||[]).filter(b=> (b.clientEmail&&c.email&&b.clientEmail===c.email) || (b.client && b.client===full));
  const term=c.moves.filter(m=>m.statut==='termine'); const aov=term.length?Math.round(term.reduce((a,m)=>a+iNum(m.montant),0)/term.length):0;
  const histo=[...c.moves].sort((a,b)=>(b.date||'').localeCompare(a.date||''));
  const fmtBoxDuree=(b)=> b.libre?'indéterminée':((b.months||'?')+' mois');
  const node=(
    <div onMouseDown={e=>{ if(e.target===e.currentTarget) onClose(); }} style={{ position:'fixed', inset:0, zIndex:1100, background:'rgba(11,31,43,.55)', display:'flex', alignItems:'center', justifyContent:'center', padding:24 }}>
      <div onMouseDown={e=>e.stopPropagation()} style={{ width:'min(640px,100%)', maxHeight:'92vh', background:'var(--card)', borderRadius:16, boxShadow:'var(--shadow-pop)', display:'flex', flexDirection:'column', overflow:'hidden' }}>
        <div style={{ padding:'18px 22px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', gap:14 }}>
          <Avatar initials={iInitials(c.prenom,c.nom)} size={46} color="var(--navy)" />
          <div style={{ flex:1, minWidth:0 }}>
            <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}><span style={{ fontSize:17, fontWeight:700, color:'var(--ink)' }}>{full}</span>
              {c.fidele && <span style={{ fontSize:10.5, fontWeight:700, color:'#C77A0A', background:'rgba(199,122,10,.12)', padding:'1px 7px', borderRadius:999 }}>Fidèle</span>}
              {c.premium && <span style={{ fontSize:10.5, fontWeight:700, color:'#B63D24', background:'rgba(182,61,36,.12)', padding:'1px 7px', borderRadius:999 }}>Premium</span>}</div>
            <div style={{ fontSize:12.5, color:'var(--muted)', marginTop:2 }}>{[c.tel,c.email].filter(Boolean).join(' · ')||'—'} · {I_SOURCES[c.source]||'Direct'}</div>
          </div>
          <div style={{ display:'flex', gap:8 }}>
            {c.tel && <a href={'tel:'+String(c.tel).replace(/\s/g,'')} className="btn btn-ghost btn-sm"><Icon name="phone" size={14} />Appeler</a>}
            <button className="btn btn-primary btn-sm" onClick={onClose}>Fermer</button>
          </div>
        </div>
        <div className="scroll" style={{ flex:1, minHeight:0, overflowY:'auto', padding:'18px 22px' }}>
          <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:10, marginBottom:18 }}>
            {[['CA cumulé',euro(c.ca),'#16965A'],['Déménagements',String(c.moves.length),'#2563EB'],['Panier moyen',aov?euro(aov):'—','#14384E'],['Avis','★ '+(c.avis.length),'#C2992E']].map(([l,v,col],i)=>(
              <div key={i} style={{ background:'var(--bg)', borderRadius:11, padding:'12px', textAlign:'center' }}><div style={{ fontSize:17, fontWeight:800, color:col }}>{v}</div><div style={{ fontSize:10, fontWeight:700, textTransform:'uppercase', letterSpacing:'.04em', color:'var(--muted)', marginTop:3 }}>{l}</div></div>))}
          </div>
          <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', marginBottom:8 }}>📦 Box loués {boxes.length>0&&'· '+boxes.length}</div>
          {boxes.length===0 ? <div style={{ fontSize:12.5, color:'var(--muted)', fontStyle:'italic', marginBottom:16 }}>Aucun box réservé.</div> :
            <div style={{ display:'flex', flexDirection:'column', gap:8, marginBottom:18 }}>{boxes.map(b=>(
              <div key={b.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 12px', borderRadius:10, background:'var(--bg)' }}>
                <span style={{ fontSize:18 }}>📦</span>
                <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:13.5, fontWeight:600, color:'var(--ink)' }}>Box {b.tier} · {b.size}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{fmtBoxDuree(b)} · dès {b.startDate?iFmtDate(b.startDate):'—'}</div></div>
                <div style={{ textAlign:'right' }}><div className="tnum" style={{ fontWeight:700, fontSize:13.5 }}>{euro(b.price)}<span style={{ fontSize:11, color:'var(--muted)' }}>/m</span></div><span style={{ fontSize:10.5, fontWeight:700, color:b.statut==='actif'?'#16965A':'#C77A0A' }}>{b.statut==='actif'?'Actif':'Demande'}</span></div>
              </div>))}</div>}
          <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', marginBottom:8 }}>🚚 Historique des déménagements</div>
          <div style={{ display:'flex', flexDirection:'column', gap:7 }}>{histo.map(m=>(
            <div key={m.id} onClick={()=>{ onClose(); openMove&&openMove(m); }} className="hoverrow" style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 12px', borderRadius:10, background:'var(--bg)', cursor:'pointer' }}>
              <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:13.5, fontWeight:600, color:'var(--ink)' }}>{m.dep.ville||'?'} → {m.arr.ville||'?'}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{iFmtDate(m.date)} · {(I_FORMULES[m.formule]||{}).label||m.formule}</div></div>
              <IPill s={m.statut} />
              <div className="tnum" style={{ fontWeight:700, fontSize:13.5, color:'var(--ink)', minWidth:64, textAlign:'right' }}>{euro(iNum(m.montant))}</div>
            </div>))}</div>
        </div>
      </div>
    </div>
  );
  return (window.ReactDOM && ReactDOM.createPortal) ? ReactDOM.createPortal(node, document.body) : node;
}
function IClients({ openMove }){
  const db=useInt(); const [q,setQ]=uIS(''); const [filt,setFilt]=uIS('tous'); const [sel,setSel]=uIS(null);
  const clients=iBuildClients(db.moves);
  const avisAll=clients.flatMap(c=>c.avis);
  let list=clients.filter(c=>`${c.prenom} ${c.nom} ${c.email}`.toLowerCase().includes(q.toLowerCase()));
  if(filt==='nouveau') list=list.filter(c=>c.moves.length===1);
  if(filt==='fidele') list=list.filter(c=>c.fidele);
  if(filt==='premium') list=list.filter(c=>c.premium);
  const kpis=[ {l:'Clients',v:clients.length,c:'#2563EB'}, {l:'CA cumulé',v:euro(clients.reduce((a,c)=>a+c.ca,0)),c:'#16965A'}, {l:'Premium',v:clients.filter(c=>c.premium).length,c:'#B63D24'}, {l:'Fidèles',v:clients.filter(c=>c.fidele).length,c:'#C77A0A'}, {l:'Avis reçus',v:'★ '+avisAll.length,c:'#C2992E'} ];
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(5,1fr)', gap:14 }}>
        {kpis.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'16px', textAlign:'center' }}>
          <div style={{ fontSize:22, fontWeight:800, color:k.c }}>{k.v}</div>
          <div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)', marginTop:5 }}>{k.l}</div></div>))}
      </div>
      <div style={{ display:'flex', alignItems:'center', gap:12, flexWrap:'wrap' }}>
        <div style={{ position:'relative', flex:1, maxWidth:280 }}><span style={{ position:'absolute', left:12, top:11, color:'var(--muted)' }}><Icon name="search" size={16} /></span>
          <IInput value={q} onChange={e=>setQ(e.target.value)} placeholder="Rechercher…" style={{ paddingLeft:36 }} /></div>
        <div style={{ display:'flex', gap:7 }}>{[['tous','Tous'],['nouveau','Nouveau'],['fidele','Fidèle'],['premium','Premium']].map(([k,l])=>(
          <button key={k} onClick={()=>setFilt(k)} style={{ fontSize:13, fontWeight:600, padding:'8px 14px', borderRadius:999, border:'1px solid', borderColor:filt===k?'var(--brand)':'var(--border-2)', background:filt===k?'var(--brand)':'var(--card)', color:filt===k?'#fff':'var(--ink-2)' }}>{l}</button>))}</div>
        <span style={{ marginLeft:'auto', fontSize:12.5, color:'var(--muted)' }}>{list.length} client{list.length>1?'s':''}</span>
      </div>
      {list.length===0 ? <div className="card card-pad"><IEmpty pad={40}>Aucun client. Les clients sont générés automatiquement depuis les déménagements (devis accepté et au-delà).</IEmpty></div> :
        <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill,minmax(300px,1fr))', gap:14 }}>
          {list.map((c,i)=>(
            <div key={i} onClick={()=>setSel(c)} className="card hoverrow" style={{ overflow:'hidden', cursor:'pointer' }}>
              <div style={{ padding:'16px', display:'flex', gap:12, alignItems:'flex-start' }}>
                <Avatar initials={iInitials(c.prenom,c.nom)} size={44} color="var(--navy)" />
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ display:'flex', alignItems:'center', gap:7, flexWrap:'wrap' }}><span style={{ fontSize:15, fontWeight:700, color:'var(--ink)' }}>{c.prenom} {c.nom}</span>
                    {c.fidele && <span style={{ fontSize:10.5, fontWeight:700, color:'#C77A0A', background:'rgba(199,122,10,.12)', padding:'1px 7px', borderRadius:999 }}>Fidèle</span>}
                    {c.premium && <span style={{ fontSize:10.5, fontWeight:700, color:'#B63D24', background:'rgba(182,61,36,.12)', padding:'1px 7px', borderRadius:999 }}>Premium</span>}</div>
                  <div style={{ fontSize:12, color:'var(--muted)', marginTop:3 }}>{I_SOURCES[c.source]||'Direct'} · {c.email||c.tel}</div>
                </div>
                <div style={{ textAlign:'right' }}><div className="tnum" style={{ fontSize:15, fontWeight:700, color:'#16965A' }}>{euro(c.ca)}</div><div style={{ fontSize:10.5, color:'var(--muted)' }}>CA</div></div>
              </div>
              <div style={{ display:'grid', gridTemplateColumns:'repeat(3,1fr)', borderTop:'1px solid var(--border)', textAlign:'center' }}>
                <div style={{ padding:'10px' }}><div className="tnum" style={{ fontSize:15, fontWeight:700, color:'#2563EB' }}>{c.moves.length}</div><div style={{ fontSize:10, color:'var(--muted)' }}>déménag.</div></div>
                <div style={{ padding:'10px', borderLeft:'1px solid var(--border)' }}><div className="tnum" style={{ fontSize:15, fontWeight:700, color:'#16965A' }}>{c.moves.filter(m=>m.statut==='termine').length}</div><div style={{ fontSize:10, color:'var(--muted)' }}>terminés</div></div>
                <div style={{ padding:'10px', borderLeft:'1px solid var(--border)' }}><div className="tnum" style={{ fontSize:13, fontWeight:700, color:'var(--ink)' }}>{iFmtDate([...c.moves].sort((a,b)=>(b.date||'').localeCompare(a.date||''))[0].date)}</div><div style={{ fontSize:10, color:'var(--muted)' }}>dernier</div></div>
              </div>
            </div>))}
        </div>}
      {sel && <IClientDetail client={sel} onClose={()=>setSel(null)} openMove={openMove} />}
    </div>
  );
}

/* ============================================================ OPÉRATIONNEL */
function ICamionDrawer({ cam, onClose, showToast }){
  const db=useInt(); const isNew=!db.camions.some(x=>x.id===cam.id);
  const [c,setC]=uIS(()=>JSON.parse(JSON.stringify(cam))); const f=(k,v)=>setC(s=>({...s,[k]:v}));
  const save=()=>{ if(!c.nom.trim()){ showToast('Nom requis'); return; } iCommit(db=>{ const i=db.camions.findIndex(x=>x.id===c.id); if(i>=0) db.camions[i]=c; else db.camions.push(c); }); showToast('Camion enregistré'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.camions=db.camions.filter(x=>x.id!==c.id); }); showToast('Camion supprimé'); onClose(); };
  const modeles = I_CAMION_MARQUES[c.marque]||[];
  const ents = c.entretiens||[];
  const kmNow = iCamionKm(c.id);
  const setEnt=(i,k,v)=>{ const a=[...ents]; a[i]={...a[i],[k]:v}; f('entretiens',a); };
  const addEnt=()=>f('entretiens',[...ents, { type:'', moisInterval:'', kmInterval:'', dernierDate:'', dernierKm:'', prochainDate:'' }]);
  const delEnt=(i)=>f('entretiens', ents.filter((_,j)=>j!==i));
  return (<IDrawer title={isNew?'Nouveau camion':c.nom} onClose={onClose} width={620} centered
    footer={<>{!isNew?<button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}>Supprimer</button>:<span />}<div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>Enregistrer</button></div></>}>
    <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', marginBottom:10 }}>🚛 Véhicule</div>
    <IField label="Nom / surnom du camion"><IInput value={c.nom} onChange={e=>f('nom',e.target.value)} placeholder="ex : Le gros, Camion 1…" /></IField>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:12 }}>
      <IField label="Marque"><ISelect value={c.marque||''} onChange={e=>{ f('marque',e.target.value); f('modele',''); }}><option value="">— Choisir —</option>{Object.keys(I_CAMION_MARQUES).map(m=><option key={m}>{m}</option>)}</ISelect></IField>
      <IField label="Modèle">{modeles.length? <ISelect value={c.modele||''} onChange={e=>f('modele',e.target.value)}><option value="">— Choisir —</option>{modeles.map(m=><option key={m}>{m}</option>)}</ISelect> : <IInput value={c.modele||''} onChange={e=>f('modele',e.target.value)} placeholder="Modèle" />}</IField>
    </div>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:12 }}>
      <IField label="Motorisation"><ISelect value={c.motorisation||''} onChange={e=>f('motorisation',e.target.value)}><option value="">— Choisir —</option>{I_MOTORISATIONS.map(m=><option key={m}>{m}</option>)}</ISelect></IField>
      <IField label="Taille / gabarit"><ISelect value={c.taille||''} onChange={e=>f('taille',e.target.value)}><option value="">— Choisir —</option>{I_TAILLES_CAMION.map(t=><option key={t}>{t}</option>)}</ISelect></IField>
    </div>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:12, marginTop:12 }}>
      <IField label="Immatriculation"><IInput value={c.immat} onChange={e=>f('immat',e.target.value)} /></IField>
      <IField label="Volume (m³)"><IInput type="number" value={c.volume} onChange={e=>f('volume',e.target.value)} /></IField>
      <IField label="Conso (L/100km)"><IInput type="number" step="0.5" value={c.conso} onChange={e=>f('conso',iNum(e.target.value))} placeholder="ex : 14" /></IField>
    </div>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:12, marginTop:12 }}>
      <IField label="Charge utile (kg)"><IInput type="number" value={c.chargeUtile} onChange={e=>f('chargeUtile',iNum(e.target.value))} placeholder="ex : 1200" /></IField>
      <IField label="Kilométrage actuel"><IInput type="number" value={c.kmInitial} onChange={e=>f('kmInitial',iNum(e.target.value))} /></IField>
      <IField label="État"><ISelect value={c.etat} onChange={e=>f('etat',e.target.value)}><option value="dispo">Disponible</option><option value="entretien">En entretien</option><option value="panne">En panne</option></ISelect></IField>
    </div>
    <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:6 }}>Compteur actuel : <b className="tnum">{kmNow.toLocaleString('fr-FR')} km</b> (les déménagements terminés s'ajoutent automatiquement).</div>

    <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', margin:'22px 0 4px' }}>
      <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)' }}>🔧 Carnet d'entretien</div>
      <div style={{ display:'flex', gap:8 }}>
        {ents.length===0 && <button type="button" className="btn btn-ghost btn-sm" onClick={()=>f('entretiens',iDefautEntretiens())}>Charger le modèle constructeur</button>}
        <button type="button" className="btn btn-ghost btn-sm" onClick={addEnt}><Icon name="plus" size={13} />Entretien</button>
      </div>
    </div>
    <div style={{ fontSize:11.5, color:'var(--muted)', marginBottom:10 }}>Renseigne la date (et/ou le km) du dernier entretien, ajuste les intervalles si besoin. La prochaine échéance et les alertes se calculent toutes seules.</div>
    {ents.length===0 ? <IEmpty pad={14}>Aucun entretien suivi. Charge le modèle constructeur ou ajoute le tien.</IEmpty> :
      <div style={{ display:'flex', flexDirection:'column', gap:10 }}>{ents.map((e,i)=>{
        const ech=iEntretienEcheance(e,kmNow); const today=iToday();
        let statut='ok'; if(ech.dueDate && iDaysBetween(today,ech.dueDate)<0) statut='retard'; else if(ech.kmRestant!=null && ech.kmRestant<0) statut='retard'; else if(ech.dueDate && iDaysBetween(today,ech.dueDate)<=30) statut='bientot'; else if(ech.kmRestant!=null && ech.kmRestant<=2000) statut='bientot';
        const col=statut==='retard'?'#C2362B':statut==='bientot'?'#C77A0A':'#16965A';
        return (
        <div key={i} style={{ padding:'12px 14px', borderRadius:11, border:'1px solid var(--border)', background:'var(--bg)' }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:10 }}>
            <Icon name="settings" size={15} style={{ color:'var(--navy)', flexShrink:0 }} />
            <IInput value={e.type} onChange={ev=>setEnt(i,'type',ev.target.value)} placeholder="Type d'entretien" style={{ flex:1, height:32, fontWeight:600 }} />
            {(ech.dueDate||ech.kmRestant!=null) && <span style={{ fontSize:11, fontWeight:700, color:col, background:col+'1f', padding:'3px 9px', borderRadius:999, whiteSpace:'nowrap' }}>{statut==='retard'?'⚠ En retard':statut==='bientot'?'⏳ Bientôt':'✓ À jour'}</span>}
            <button type="button" onClick={()=>delEnt(i)} style={{ color:'var(--muted)', display:'flex' }}><Icon name="x" size={14} /></button>
          </div>
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr 1fr', gap:9 }}>
            <IField label="Dernier fait"><IInput type="date" value={e.dernierDate} onChange={ev=>setEnt(i,'dernierDate',ev.target.value)} style={{ height:32 }} /></IField>
            <IField label="Km au dernier"><IInput type="number" value={e.dernierKm} onChange={ev=>setEnt(i,'dernierKm',ev.target.value)} placeholder="km" style={{ height:32 }} /></IField>
            <IField label="Tous les (mois)"><IInput type="number" value={e.moisInterval} onChange={ev=>setEnt(i,'moisInterval',ev.target.value)} placeholder="mois" style={{ height:32 }} /></IField>
            <IField label="Tous les (km)"><IInput type="number" value={e.kmInterval} onChange={ev=>setEnt(i,'kmInterval',ev.target.value)} placeholder="km" style={{ height:32 }} /></IField>
          </div>
          <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:8, display:'flex', gap:14, flexWrap:'wrap' }}>
            {ech.dueDate && <span>📅 Prochain : <b style={{ color:'var(--ink-2)' }}>{iFmtDate(ech.dueDate)}</b></span>}
            {ech.kmRestant!=null && <span>🛣 Reste : <b style={{ color:'var(--ink-2)' }}>{ech.kmRestant.toLocaleString('fr-FR')} km</b></span>}
            <label style={{ display:'flex', alignItems:'center', gap:5 }}>Date imposée : <IInput type="date" value={e.prochainDate} onChange={ev=>setEnt(i,'prochainDate',ev.target.value)} style={{ height:28, width:140 }} /></label>
          </div>
        </div>); })}</div>}
  </IDrawer>); }
function IEmpDrawer({ emp, onClose, showToast }){
  const db=useInt(); const isNew=!db.equipe.some(x=>x.id===emp.id);
  const [e,setE]=uIS(()=>JSON.parse(JSON.stringify(emp))); const f=(k,v)=>setE(s=>({...s,[k]:v}));
  const save=()=>{ if(!e.prenom.trim()){ showToast('Prénom requis'); return; } iCommit(db=>{ const i=db.equipe.findIndex(x=>x.id===e.id); if(i>=0) db.equipe[i]=e; else db.equipe.push(e); }); showToast('Membre enregistré'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.equipe=db.equipe.filter(x=>x.id!==e.id); }); showToast('Membre supprimé'); onClose(); };
  return (<IDrawer title={isNew?'Nouvel employé':e.prenom+' '+e.nom} onClose={onClose} width={460}
    footer={<>{!isNew?<button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}>Supprimer</button>:<span />}<div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>Enregistrer</button></div></>}>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
      <IField label="Prénom"><IInput value={e.prenom} onChange={ev=>f('prenom',ev.target.value)} /></IField>
      <IField label="Nom"><IInput value={e.nom} onChange={ev=>f('nom',ev.target.value)} /></IField>
    </div>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:12 }}>
      <IField label="Rôle"><ISelect value={e.role} onChange={ev=>f('role',ev.target.value)}><option value="chef">Chef d'équipe</option><option value="demenageur">Déménageur</option><option value="chauffeur">Chauffeur</option></ISelect></IField>
      <IField label="Téléphone"><IInput value={e.tel} onChange={ev=>f('tel',ev.target.value)} /></IField>
    </div>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:12 }}>
      <IField label="Permis"><ISelect value={e.permis} onChange={ev=>f('permis',ev.target.value)}><option>B</option><option>C</option><option>CE</option></ISelect></IField>
      <IField label="Niveau d'expérience"><ISelect value={e.niveauExp||'confirme'} onChange={ev=>f('niveauExp',ev.target.value)}><option value="debutant">Débutant</option><option value="confirme">Confirmé</option><option value="expert">Expert</option></ISelect></IField>
    </div>
    <div style={{ fontSize:13, fontWeight:700, color:'var(--ink)', margin:'18px 0 10px' }}>💶 Rémunération</div>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
      <IField label="Tarif (€)"><IInput type="number" value={e.tarif} onChange={ev=>f('tarif',ev.target.value)} placeholder="ex : 150" /></IField>
      <IField label="Payé à la"><ISelect value={e.tarifType||'jour'} onChange={ev=>f('tarifType',ev.target.value)}><option value="jour">Journée</option><option value="heure">Heure</option><option value="chantier">Chantier</option></ISelect></IField>
    </div>
    <div style={{ display:'flex', gap:20, marginTop:14, flexWrap:'wrap' }}>
      <label style={{ display:'flex', alignItems:'center', gap:9, fontSize:13.5, color:'var(--ink-2)', cursor:'pointer' }}><input type="checkbox" checked={!!e.aCamion} onChange={ev=>f('aCamion',ev.target.checked)} />A son propre camion</label>
      <label style={{ display:'flex', alignItems:'center', gap:9, fontSize:13.5, color:'var(--ink-2)', cursor:'pointer' }}><input type="checkbox" checked={!!e.actif} onChange={ev=>f('actif',ev.target.checked)} />Actif</label>
    </div>
  </IDrawer>); }
function IMaterielDrawer({ mat, onClose, showToast }){
  const db=useInt(); const isNew=!db.materiel.some(x=>x.id===mat.id);
  const [m,setM]=uIS(()=>JSON.parse(JSON.stringify(mat))); const f=(k,v)=>setM(s=>({...s,[k]:v}));
  const save=()=>{ if(!m.nom.trim()){ showToast('Nom requis'); return; } iCommit(db=>{ const i=db.materiel.findIndex(x=>x.id===m.id); if(i>=0) db.materiel[i]=m; else db.materiel.push(m); }); showToast('Matériel enregistré'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.materiel=db.materiel.filter(x=>x.id!==m.id); }); showToast('Matériel supprimé'); onClose(); };
  return (<IDrawer title={isNew?'Nouveau matériel':m.nom} onClose={onClose} width={440}
    footer={<>{!isNew?<button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}>Supprimer</button>:<span />}<div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>Enregistrer</button></div></>}>
    <IField label="Nom"><IInput value={m.nom} onChange={e=>f('nom',e.target.value)} placeholder="Couvertures, sangles…" /></IField>
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:12, marginTop:14 }}>
      <IField label="Unités"><IInput type="number" value={m.unites} onChange={e=>f('unites',iNum(e.target.value))} /></IField>
      <IField label="Prix / u (€)"><IInput type="number" value={m.prix} onChange={e=>f('prix',iNum(e.target.value))} /></IField>
      <IField label="Seuil bas"><IInput type="number" value={m.seuil} onChange={e=>f('seuil',iNum(e.target.value))} /></IField>
    </div>
  </IDrawer>); }
function IOperationnel({ showToast }){
  const db=useInt(); const [tab,setTab]=uIS('ressources'); const [weekOff,setWeekOff]=uIS(0);
  const [camEdit,setCamEdit]=uIS(null); const [empEdit,setEmpEdit]=uIS(null); const [matEdit,setMatEdit]=uIS(null);
  const today=iToday();
  const actifs=db.equipe.filter(e=>e.actif).length;
  const dispo=db.camions.filter(c=>c.etat==='dispo').length;
  const alertesFlotte=iToutesAlertesFlotte(); const stockBas=iStockBas(); const alertes=alertesFlotte.length+stockBas.length;
  const chantiers=db.moves.filter(m=>m.date>=today && ['confirme','attente_paiement'].includes(m.statut)).length;
  const kpis=[ {l:'Employés actifs',v:actifs+'/'+db.equipe.length}, {l:'Camions dispos',v:dispo+'/'+db.camions.length}, {l:'Alertes entretien',v:String(alertes),t:alertes?'a':''}, {l:'Chantiers à venir',v:String(chantiers)} ];
  const adjust=(m,delta)=>iCommit(db=>{ const x=db.materiel.find(v=>v.id===m.id); if(x) x.unites=Math.max(0,iNum(x.unites)+delta); });
  const futureMoves=db.moves.filter(m=>m.date>=today && !['refuse','annule'].includes(m.statut)).sort((a,b)=>a.date.localeCompare(b.date));

  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, flexWrap:'wrap' }}>
        <ITabs value={tab} onChange={setTab} tabs={[{k:'ressources',label:'🔧 Ressources'},{k:'planning',label:'🗓️ Planning'}]} />
        <div style={{ display:'flex', gap:8 }}>
          <button className="btn btn-ghost btn-sm" onClick={()=>setCamEdit({ id:iUid('CAM'), nom:'', marque:'', modele:'', motorisation:'', taille:'', immat:'', volume:'', conso:'', chargeUtile:'', etat:'dispo', entretiens:iDefautEntretiens() })}>🚛 + Camion</button>
          <button className="btn btn-ghost btn-sm" onClick={()=>setEmpEdit({ id:iUid('EMP'), prenom:'', nom:'', tel:'', role:'demenageur', permis:'B', tarif:'', tarifType:'jour', aCamion:false, niveauExp:'confirme', actif:true })}>👷 + Employé</button>
          <button className="btn btn-ghost btn-sm" onClick={()=>setMatEdit({ id:iUid('M'), nom:'', unites:0, prix:0, seuil:1 })}>📦 + Matériel</button>
        </div>
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:14 }}>
        {kpis.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'14px 16px' }}>
          <div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.06em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div>
          <div className="tnum" style={{ fontSize:22, fontWeight:800, marginTop:6, color:k.t==='a'?'#C77A0A':'var(--ink)' }}>{k.v}</div></div>))}
      </div>

      {tab==='ressources' ? (<>
        {(alertesFlotte.length>0 || stockBas.length>0) && <div className="card card-pad" style={{ borderColor:alertesFlotte.some(a=>a.statut==='retard')?'#C2362B':'#C77A0A' }}>
          <div style={{ fontSize:12.5, fontWeight:700, color:'var(--ink)', marginBottom:10 }}>🔔 Alertes ({alertesFlotte.length+stockBas.length})</div>
          <div style={{ display:'flex', flexDirection:'column', gap:7 }}>
            {alertesFlotte.map((a,i)=>{ const ret=a.statut==='retard'; const col=ret?'#C2362B':'#C77A0A'; return (
              <div key={'f'+i} onClick={()=>{ const c=db.camions.find(x=>x.id===a.camId); if(c) setCamEdit(c); }} style={{ display:'flex', alignItems:'center', gap:10, padding:'9px 11px', borderRadius:9, background:col+'12', cursor:'pointer' }}>
                <span style={{ fontSize:15 }}>{ret?'⚠️':'⏳'}</span>
                <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:13, fontWeight:600, color:'var(--ink)' }}>{a.type} · {a.camNom}</div><div style={{ fontSize:11.5, color:col, fontWeight:600 }}>{a.label}</div></div>
                <Icon name="chevronR" size={15} style={{ color:'var(--muted)' }} />
              </div>); })}
            {stockBas.map((m,i)=>(
              <div key={'s'+i} onClick={()=>setMatEdit(m)} style={{ display:'flex', alignItems:'center', gap:10, padding:'9px 11px', borderRadius:9, background:'rgba(194,54,43,.07)', cursor:'pointer' }}>
                <span style={{ fontSize:15 }}>📦</span>
                <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:13, fontWeight:600, color:'var(--ink)' }}>Stock bas : {m.nom}</div><div style={{ fontSize:11.5, color:'#C2362B', fontWeight:600 }}>{m.unites} restant(s) · seuil {m.seuil} · à renflouer</div></div>
                <Icon name="chevronR" size={15} style={{ color:'var(--muted)' }} />
              </div>))}
          </div>
        </div>}
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
          <div><div style={{ fontSize:12.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)', marginBottom:10 }}>🚛 Flotte de camions</div>
            {db.camions.length===0 ?
              <button onClick={()=>setCamEdit({ id:iUid('CAM'), nom:'', marque:'', modele:'', motorisation:'', taille:'', immat:'', volume:'', conso:'', chargeUtile:'', etat:'dispo', entretiens:iDefautEntretiens() })} className="card" style={{ width:'100%', padding:'40px 16px', border:'2px dashed var(--border-2)', background:'transparent', color:'var(--muted)' }}>🚛<div style={{ marginTop:8, fontWeight:600 }}>Ajouter un camion</div></button> :
              <div style={{ display:'flex', flexDirection:'column', gap:10 }}>{db.camions.map(c=>{ const al=iCamionEntretiensDus(c); const retard=al.some(a=>a.statut==='retard'); return (
                <div key={c.id} onClick={()=>setCamEdit(c)} className="card card-pad" style={{ cursor:'pointer', display:'flex', alignItems:'center', gap:13, borderColor:retard?'#C2362B':undefined }}>
                  <span style={{ width:42, height:42, borderRadius:11, background:'var(--navy)', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center' }}><Icon name="truck" size={20} /></span>
                  <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:14, fontWeight:700, color:'var(--ink)' }}>{c.nom}{(c.marque||c.modele)?<span style={{ fontWeight:500, color:'var(--muted)' }}> · {c.marque} {c.modele}</span>:''}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{c.immat||'—'} · {c.volume||'?'} m³ · <span className="tnum" style={{ color:'var(--ink-2)', fontWeight:600 }}>{iCamionKm(c.id).toLocaleString('fr-FR')} km</span>{al.length>0 && <span style={{ color:retard?'#C2362B':'#C77A0A', fontWeight:600 }}> · {retard?'⚠ '+al.length+' entretien(s)':'⏳ '+al.length+' bientôt'}</span>}</div></div>
                  <span style={{ fontSize:11.5, fontWeight:600, padding:'3px 9px', borderRadius:999, background:c.etat==='dispo'?'rgba(22,150,90,.12)':'rgba(199,122,10,.12)', color:c.etat==='dispo'?'#16965A':'#C77A0A' }}>{c.etat==='dispo'?'Disponible':c.etat==='entretien'?'Entretien':'Panne'}</span>
                </div>); })}
                <button onClick={()=>setCamEdit({ id:iUid('CAM'), nom:'', marque:'', modele:'', motorisation:'', taille:'', immat:'', volume:'', conso:'', chargeUtile:'', etat:'dispo', entretiens:iDefautEntretiens() })} className="btn btn-ghost btn-sm" style={{ width:'100%' }}><Icon name="plus" size={14} />Ajouter un camion</button>
              </div>}
          </div>
          <div><div style={{ fontSize:12.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)', marginBottom:10 }}>👷 Équipe</div>
            {db.equipe.length===0 ?
              <button onClick={()=>setEmpEdit({ id:iUid('EMP'), prenom:'', nom:'', tel:'', role:'demenageur', permis:'B', tarif:'', tarifType:'jour', aCamion:false, niveauExp:'confirme', actif:true })} className="card" style={{ width:'100%', padding:'40px 16px', border:'2px dashed var(--border-2)', background:'transparent', color:'var(--muted)' }}>👷<div style={{ marginTop:8, fontWeight:600 }}>Ajouter un employé</div></button> :
              <div style={{ display:'flex', flexDirection:'column', gap:10 }}>{db.equipe.map(e=>(
                <div key={e.id} onClick={()=>setEmpEdit(e)} className="card card-pad" style={{ cursor:'pointer', display:'flex', alignItems:'center', gap:13 }}>
                  <Avatar initials={iInitials(e.prenom,e.nom)} size={42} color="var(--brand)" />
                  <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:14, fontWeight:700, color:'var(--ink)' }}>{e.prenom} {e.nom}{e.aCamion && <span title="A son camion" style={{ marginLeft:6 }}>🚛</span>}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{e.role==='chef'?"Chef d'équipe":e.role==='chauffeur'?'Chauffeur':'Déménageur'}{e.niveauExp?' · '+(e.niveauExp==='debutant'?'Débutant':e.niveauExp==='expert'?'Expert':'Confirmé'):''}{iNum(e.tarif)>0?' · '+euro(iNum(e.tarif))+'/'+(e.tarifType==='heure'?'h':e.tarifType==='chantier'?'chantier':'j'):''} · <span style={{ color:'#16965A', fontWeight:600 }}>{iEmployeMoves(e.id)} déménag.</span></div></div>
                  {!e.actif && <span style={{ fontSize:11, color:'var(--muted)' }}>Inactif</span>}
                </div>))}
                <button onClick={()=>setEmpEdit({ id:iUid('EMP'), prenom:'', nom:'', tel:'', role:'demenageur', permis:'B', tarif:'', tarifType:'jour', aCamion:false, niveauExp:'confirme', actif:true })} className="btn btn-ghost btn-sm" style={{ width:'100%' }}><Icon name="plus" size={14} />Ajouter un employé</button>
              </div>}
          </div>
        </div>

        <div><div style={{ fontSize:12.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)', marginBottom:10 }}>📦 Stock matériel</div>
          <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill,minmax(220px,1fr))', gap:14 }}>
            {db.materiel.map(m=>{ const bas=iNum(m.unites)<=iNum(m.seuil); return (
              <div key={m.id} className="card card-pad" style={{ border:bas?'1px solid #C2362B':'1px solid var(--border)' }}>
                <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start' }}>
                  <span onClick={()=>setMatEdit(m)} style={{ cursor:'pointer', display:'flex' }}><Icon name="package" size={18} style={{ color:'var(--navy)' }} /></span>
                  {bas && <span style={{ fontSize:10.5, fontWeight:700, color:'#C2362B', background:'rgba(194,54,43,.12)', padding:'2px 8px', borderRadius:999 }}>⚠️ Bas</span>}
                </div>
                <div onClick={()=>setMatEdit(m)} style={{ fontSize:14, fontWeight:700, color:'var(--ink)', marginTop:8, cursor:'pointer' }}>{m.nom}</div>
                <div className="tnum" style={{ fontSize:26, fontWeight:800, color:bas?'#C2362B':'var(--ink)', marginTop:4 }}>{m.unites}</div>
                <div style={{ fontSize:11.5, color:'var(--muted)' }}>unités · {m.prix} €/u · seuil {m.seuil}</div>
                <div style={{ display:'flex', gap:8, marginTop:12 }}>
                  <button onClick={()=>adjust(m,-1)} className="btn btn-ghost btn-sm" style={{ flex:1, color:'#C2362B' }}>−</button>
                  <button onClick={()=>adjust(m,1)} className="btn btn-ghost btn-sm" style={{ flex:1, color:'#16965A' }}>+</button>
                </div>
              </div>); })}
            <button onClick={()=>setMatEdit({ id:iUid('M'), nom:'', unites:0, prix:0, seuil:1 })} className="card" style={{ padding:'30px 16px', border:'2px dashed var(--border-2)', background:'transparent', color:'var(--muted)', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:6 }}><Icon name="plus" size={22} /><span style={{ fontWeight:600 }}>Ajouter</span></button>
          </div>
        </div>
      </>) : (()=>{
        const mon=(()=>{ const d=new Date(today+'T00:00:00'); let dow=d.getDay(); dow=dow===0?6:dow-1; d.setDate(d.getDate()-dow+weekOff*7); return d; })();
        const days=[...Array(7)].map((_,i)=>{ const d=new Date(mon); d.setDate(mon.getDate()+i); return d.toISOString().slice(0,10); });
        const DOW=['Lun','Mar','Mer','Jeu','Ven','Sam','Dim'];
        const onDay=(ds)=>db.moves.filter(m=>m.date===ds && !['refuse','annule'].includes(m.statut));
        const usesCam=(m,id)=>(m.camions||[]).some(c=>c.id===id);
        const usesEmp=(m,id)=>((m.mainOeuvre||[]).some(p=>p.id===id))||((m.equipe||[]).includes(id));
        const cli=(m)=>((m.prenom||'')+' '+(m.nom||'')).trim()||m.id;
        const resCam=db.camions.map(c=>({ id:c.id, label:c.nom||('Camion '+(c.immat||'')), type:'cam' }));
        const resEmp=db.equipe.map(e=>({ id:e.id, label:((e.prenom||'')+' '+(e.nom||'')).trim()||'Employé', type:'emp' }));
        const lbl=(d)=>{ const dt=new Date(d+'T00:00:00'); return DOW[(dt.getDay()+6)%7]+' '+dt.getDate(); };
        const Row=({ res })=> (
          <tr>
            <td style={{ padding:'8px 12px', fontWeight:600, fontSize:13, color:'var(--ink)', position:'sticky', left:0, background:'var(--card)', whiteSpace:'nowrap' }}>{res.type==='cam'?'🚛 ':'👷 '}{res.label}</td>
            {days.map((ds,di)=>{ const ms=onDay(ds).filter(m=> res.type==='cam'?usesCam(m,res.id):usesEmp(m,res.id)); const conflit=ms.length>1; return (
              <td key={di} style={{ padding:4, borderLeft:'1px solid var(--border)', background:ds===today?'rgba(182,61,36,.04)':'transparent', verticalAlign:'top' }}>
                {ms.length===0 ? <div style={{ textAlign:'center', color:'var(--faint)', fontSize:12, padding:'6px 0' }}>—</div> :
                  ms.map(m=>(<div key={m.id} onClick={()=>window.LBC_INT.openMoveGlobal&&window.LBC_INT.openMoveGlobal(m)} title={cli(m)+' · '+(m.dep.ville||'?')+'→'+(m.arr.ville||'?')} style={{ cursor:'pointer', fontSize:10.5, fontWeight:600, padding:'4px 6px', borderRadius:6, marginBottom:3, lineHeight:1.2, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', background:conflit?'rgba(194,54,43,.16)':'rgba(182,61,36,.12)', color:conflit?'#C2362B':'var(--brand)' }}>{m.heure?m.heure+' ':''}{cli(m)}</div>))}
              </td>); })}
          </tr>
        );
        return (
        <div className="card" style={{ overflow:'hidden' }}>
          <div style={{ padding:'12px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, flexWrap:'wrap' }}>
            <span className="section-title" style={{ fontSize:15 }}>Planning de la semaine</span>
            <div style={{ display:'flex', alignItems:'center', gap:8 }}>
              <button className="btn btn-ghost btn-sm" onClick={()=>setWeekOff(w=>w-1)}><Icon name="chevronL" size={15} /></button>
              <span style={{ fontSize:12.5, fontWeight:600, color:'var(--ink-2)', minWidth:150, textAlign:'center' }}>{iFmtDate(days[0])} → {iFmtDate(days[6])}</span>
              <button className="btn btn-ghost btn-sm" onClick={()=>setWeekOff(w=>w+1)}><Icon name="chevronR" size={15} /></button>
              {weekOff!==0 && <button className="btn btn-ghost btn-sm" onClick={()=>setWeekOff(0)}>Cette semaine</button>}
            </div>
          </div>
          {(resCam.length+resEmp.length)===0 ? <IEmpty pad={30}>Ajoute des camions et des employés dans l'onglet « Ressources » pour voir leur planning.</IEmpty> :
          <div style={{ overflowX:'auto' }}>
            <table style={{ borderCollapse:'collapse', width:'100%', minWidth:760 }}>
              <thead><tr>
                <th style={{ padding:'8px 12px', textAlign:'left', fontSize:11, textTransform:'uppercase', letterSpacing:'.04em', color:'var(--muted)', position:'sticky', left:0, background:'var(--card)' }}>Ressource</th>
                {days.map((d,i)=><th key={i} style={{ padding:'8px 6px', fontSize:11.5, fontWeight:700, color:d===today?'var(--brand)':'var(--ink-2)', borderLeft:'1px solid var(--border)', textTransform:'capitalize' }}>{lbl(d)}</th>)}
              </tr></thead>
              <tbody>
                {resCam.length>0 && <tr><td colSpan={8} style={{ padding:'7px 12px', fontSize:10.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)', background:'var(--bg)' }}>Camions</td></tr>}
                {resCam.map(r=><Row key={r.id} res={r} />)}
                {resEmp.length>0 && <tr><td colSpan={8} style={{ padding:'7px 12px', fontSize:10.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)', background:'var(--bg)' }}>Équipe</td></tr>}
                {resEmp.map(r=><Row key={r.id} res={r} />)}
              </tbody>
            </table>
          </div>}
          <div style={{ padding:'10px 18px', borderTop:'1px solid var(--border)', fontSize:12, color:'var(--muted)' }}>Une case rouge = ressource réservée 2 fois le même jour (conflit). Clique un chantier pour l'ouvrir.</div>
        </div>); })()}

      {camEdit && <ICamionDrawer cam={camEdit} onClose={()=>setCamEdit(null)} showToast={showToast} />}
      {empEdit && <IEmpDrawer emp={empEdit} onClose={()=>setEmpEdit(null)} showToast={showToast} />}
      {matEdit && <IMaterielDrawer mat={matEdit} onClose={()=>setMatEdit(null)} showToast={showToast} />}
    </div>
  );
}

/* ============================================================ STATISTIQUES */
function IStatistiques(){
  const db=useInt(); const moves=db.moves;
  const term=moves.filter(m=>m.statut==='termine');
  const eng=moves.filter(m=>['confirme','attente_paiement','termine'].includes(m.statut));
  const refus=moves.filter(m=>m.statut==='refuse');
  const repondus=moves.filter(m=>m.statut!=='devis_a_envoyer'&&m.statut!=='annule');
  const conv=repondus.length?Math.round(eng.length/repondus.length*100):0;
  const caPerdu=refus.reduce((a,m)=>a+iNum(m.montant),0);
  const aov=term.length?Math.round(term.reduce((a,m)=>a+iNum(m.montant),0)/term.length):0;
  const litigesAssur=db.incidents.filter(i=>i.statut==='assurance'); const incidents=litigesAssur.length;
  const clients=iBuildClients(moves);
  const kpis=[ {l:'Taux conversion',v:conv+'%',c:conv>=50?'#16965A':'#B63D24'}, {l:'Devis refusés',v:String(refus.length),c:'#C2362B'}, {l:'CA perdu (refus)',v:euro(caPerdu),c:'#C2362B'}, {l:'Clients fidèles',v:clients.filter(c=>c.fidele).length+' ('+(clients.length?Math.round(clients.filter(c=>c.fidele).length/clients.length*100):0)+'%)',c:'#C77A0A'}, {l:'Panier moyen',v:aov?euro(aov):'—'}, {l:'Total terminés',v:String(term.length),c:'#2563EB'}, {l:'Litiges assurance',v:String(incidents),c:incidents?'#C2362B':'var(--ink)'} ];
  // sources
  const bySrc={}; moves.forEach(m=>{ const s=m.source||'autre'; if(!bySrc[s]) bySrc[s]={n:0,conv:0}; bySrc[s].n++; if(['confirme','attente_paiement','termine'].includes(m.statut)) bySrc[s].conv++; });
  const srcRows=Object.keys(bySrc).map(s=>({ s, n:bySrc[s].n, pct:Math.round(bySrc[s].conv/bySrc[s].n*100) })).sort((a,b)=>b.n-a.n);
  const srcMax=Math.max(1,...srcRows.map(r=>r.n));
  // pipeline
  const pipe=I_PIPELINE.map(s=>({ s, n:moves.filter(m=>m.statut===s).length, val:moves.filter(m=>m.statut===s).reduce((a,m)=>a+iNum(m.montant),0) }));
  const pipeMax=Math.max(1,...pipe.map(p=>p.n));
  // formules
  const byForm={}; term.forEach(m=>{ byForm[m.formule]=(byForm[m.formule]||0)+iNum(m.montant); });
  const formSegs=Object.keys(byForm).map(k=>{ const fo=I_FORMULES[k]||{label:k,c:'#8A96A0'}; return { label:fo.label, value:byForm[k], color:fo.c }; });
  // rentabilité par formule (chantiers gagnés)
  const byFormR={}; eng.forEach(m=>{ const f=m.formule; if(!byFormR[f]) byFormR[f]={n:0,ca:0,cout:0,comm:0}; byFormR[f].n++; byFormR[f].ca+=iNum(m.montant); byFormR[f].cout+=iCoutTotal(m); if(m.partenaire) byFormR[f].comm+=Math.round(iNum(m.montant)*partnerRate(m.partnerId)/100); });
  const formRows=Object.keys(byFormR).map(f=>{ const fo=I_FORMULES[f]||{label:f,c:'#8A96A0'}; const g=byFormR[f]; const netF=g.ca-g.cout-g.comm; return { label:fo.label, c:fo.c, n:g.n, ca:g.ca, net:netF, marge:g.ca?Math.round(netF/g.ca*100):0 }; }).sort((a,b)=>b.net-a.net);
  // évolution mensuelle (terminés + conv)
  const byMonth={}; moves.forEach(m=>{ const k=iMonthKey(m.date); if(!k) return; if(!byMonth[k]) byMonth[k]={term:0,refus:0}; if(m.statut==='termine') byMonth[k].term++; if(m.statut==='refuse') byMonth[k].refus++; });
  const months=Object.keys(byMonth).sort();
  const evo=months.map(k=>({ m:I_MONTHS_AB[parseInt(k.slice(5,7))-1], v:byMonth[k].term }));
  const topClients=[...clients].sort((a,b)=>b.ca-a.ca).slice(0,5);
  // acquisition réseau (partenaires)
  const byPartner={}; moves.forEach(m=>{ if(!m.partenaire) return; if(!byPartner[m.partenaire]) byPartner[m.partenaire]={n:0,ca:0,won:0}; byPartner[m.partenaire].n++; byPartner[m.partenaire].ca+=iCaEncaisse(m); if(['confirme','attente_paiement','termine'].includes(m.statut)) byPartner[m.partenaire].won++; });
  const topPartners=Object.keys(byPartner).map(k=>({ k, ...byPartner[k] })).sort((a,b)=>b.ca-a.ca).slice(0,6);
  const caPartenaires=moves.filter(m=>m.partenaire).reduce((a,m)=>a+iCaEncaisse(m),0);
  const partDirect=moves.filter(m=>!['refuse','annule'].includes(m.statut));
  const partShare=partDirect.length?Math.round(moves.filter(m=>m.partenaire&&!['refuse','annule'].includes(m.statut)).length/partDirect.length*100):0;
  // évolution (combo terminés + taux de conversion), vision année / depuis le début
  const [evoRange,setEvoRange]=uIS(String(new Date().getFullYear()));
  const allMonthsStat=(()=>{ const s=new Set(); moves.forEach(m=>m.date&&s.add(iMonthKey(m.date))); return [...s].sort(); })();
  const statYears=Array.from(new Set(allMonthsStat.map(k=>k.slice(0,4)).concat([String(new Date().getFullYear())]))).sort();
  const evoMonths=evoRange==='all'?(allMonthsStat.length?iMonthRange(allMonthsStat[0], iMonthKey(iToday())):[iMonthKey(iToday())]):iMonthRange(evoRange+'-01', evoRange+'-12');
  const evoRows=evoMonths.map(k=>{ const ms=moves.filter(x=>iMonthKey(x.date)===k); const t=ms.filter(x=>x.statut==='termine').length; const base=ms.filter(x=>x.statut!=='annule').length; const won=ms.filter(x=>['confirme','attente_paiement','termine'].includes(x.statut)).length; return { label:I_MONTHS_AB[parseInt(k.slice(5,7))-1], vol:t, conv:base?Math.round(won/base*100):0, ca:ms.reduce((a,x)=>a+iCaEncaisse(x),0) }; });
  // performance chantiers
  const avisList=moves.filter(m=>m.avis).map(m=>m.avis); const nbAvis=avisList.length;
  const satis=nbAvis?Math.round(avisList.reduce((a,v)=>a+v,0)/nbAvis/5*100):0;
  const tauxAvis=term.length?Math.round(nbAvis/term.length*100):0;
  const margeBruteMoy=term.length?Math.round(term.reduce((a,m)=>a+iBenefice(m),0)/term.length):0;
  const bestForm=[...formRows].sort((a,b)=>b.marge-a.marge)[0];
  const incidentsN=(db.incidents||[]).filter(i=>i.statut==='assurance').length;
  const indemnTotal=(db.incidents||[]).filter(i=>i.statut==='assurance').reduce((a,i)=>a+iNum(i.montant),0);
  // fidélisation & récurrence
  const nouveaux=clients.filter(c=>c.moves.length===1).length;
  const fideles=clients.filter(c=>c.fidele).length;
  const premiumC=clients.filter(c=>c.premium).length;
  const tauxFidel=clients.length?Math.round(fideles/clients.length*100):0;
  const perf=[
    { l:'Déménag. terminés', v:String(term.length) },
    { l:'Marge brute moy.', v:term.length?euro(margeBruteMoy):'—' },
    { l:'Formule + rentable', v:bestForm?bestForm.label+' ('+bestForm.marge+'% marge)':'—' },
    { l:'Taux de satisfaction', v:satis+'% ('+nbAvis+'/'+term.length+' avis)' },
    { l:'Taux d\'avis / client', v:tauxAvis+'%' },
    { l:'Litiges assurance', v:incidentsN?(incidentsN+(indemnTotal?' · '+euro(indemnTotal):'')):'✓ Aucun', c:incidentsN?'#C2362B':'#16965A' },
  ];
  // coût par lead & ROI acquisition payante (Google / Meta / Leboncoin)
  const moisSetS=new Set(); moves.forEach(m=>m.date&&moisSetS.add(iMonthKey(m.date))); db.charges.forEach(c=>c.date&&moisSetS.add(iMonthKey(c.date))); const nMoisS=Math.max(1,moisSetS.size);
  const mkt=db.charges.filter(c=>c.categorie==='Publicité / Marketing');
  const budgetPub=mkt.filter(c=>c.recurrent).reduce((a,c)=>a+iMensualise(c),0)*nMoisS + mkt.filter(c=>!c.recurrent).reduce((a,c)=>a+iNum(c.montant),0);
  const PAID=['google','meta','leboncoin'];
  const CANAL_OF={ google:'Google Ads', meta:'Meta', leboncoin:'Leboncoin' };
  // dépense pub par canal (récurrent mensualisé × nb mois + ponctuel)
  const mktByCanal={}; mkt.forEach(c=>{ const k=c.canal||'Autre'; const v=c.recurrent?iMensualise(c)*nMoisS:iNum(c.montant); mktByCanal[k]=(mktByCanal[k]||0)+v; });
  const paidMoves=moves.filter(m=>PAID.includes(m.source));
  const paidWon=paidMoves.filter(m=>['confirme','attente_paiement','termine'].includes(m.statut));
  const caPaid=paidMoves.reduce((a,m)=>a+iCaEncaisse(m),0);
  const cpl=paidMoves.length?Math.round(budgetPub/paidMoves.length):0;
  const cac=paidWon.length?Math.round(budgetPub/paidWon.length):0;
  const roas=budgetPub>0?(caPaid/budgetPub):0;
  const cplRows=PAID.map(s=>{ const ms=moves.filter(m=>m.source===s); const won=ms.filter(m=>['confirme','attente_paiement','termine'].includes(m.statut)); const dep=mktByCanal[CANAL_OF[s]]||0; const ca=ms.reduce((a,m)=>a+iCaEncaisse(m),0);
    return { s, canal:CANAL_OF[s], leads:ms.length, clients:won.length, conv:ms.length?Math.round(won.length/ms.length*100):0, ca, dep, cpl:(ms.length&&dep)?Math.round(dep/ms.length):0, cac:(won.length&&dep)?Math.round(dep/won.length):0, roas:dep?ca/dep:0 }; }).filter(r=>r.leads>0||r.dep>0);
  const acqKpis=[ {l:'Budget pub (période)',v:euro(budgetPub),c:'#C77A0A'}, {l:'Coût par lead (CPL)',v:cpl?euro(cpl):'—',c:'#2563EB'}, {l:"Coût d'acquisition client",v:cac?euro(cac):'—',c:'#B63D24'}, {l:'ROAS (CA / budget)',v:budgetPub?roas.toFixed(1)+'×':'—',c:roas>=3?'#16965A':roas>0?'#C77A0A':'var(--ink)'} ];
  // évolution mensuelle du CPL (suit le sélecteur d'année ci-dessous) + alerte cible
  const recurMktMonthly=mkt.filter(c=>c.recurrent).reduce((a,c)=>a+iMensualise(c),0);
  const cplEvo=evoMonths.map(k=>{ const dep=recurMktMonthly + mkt.filter(c=>!c.recurrent && iMonthKey(c.date)===k).reduce((a,c)=>a+iNum(c.montant),0); const ms=paidMoves.filter(m=>iMonthKey(m.date)===k); const won=ms.filter(m=>['confirme','attente_paiement','termine'].includes(m.statut)).length; return { label:I_MONTHS_AB[parseInt(k.slice(5,7))-1], cpl:ms.length?Math.round(dep/ms.length):0, leads:ms.length, dep:Math.round(dep), won }; });
  const cplCible=iNum(db.settings&&db.settings.cplCible)||0;
  const cplDernier=[...cplEvo].reverse().find(r=>r.leads>0)||null;
  const cplAlerte=cplCible>0 && cplDernier && cplDernier.cpl>cplCible;

  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(7,1fr)', gap:12 }}>
        {kpis.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'13px 14px' }}>
          <div style={{ fontSize:9.5, fontWeight:700, letterSpacing:'.04em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div>
          <div className="tnum" style={{ fontSize:19, fontWeight:800, marginTop:6, color:k.c||'var(--ink)' }}>{k.v}</div></div>))}
      </div>

      <div className="card card-pad">
        <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>🧭 Sources d'acquisition & taux de conversion</span>
        {srcRows.length===0 ? <IEmpty>Aucune donnée</IEmpty> : <div style={{ display:'flex', flexDirection:'column', gap:11 }}>{srcRows.map(r=>(
          <div key={r.s} style={{ display:'flex', alignItems:'center', gap:12 }}>
            <span style={{ width:170, fontSize:12.5, color:'var(--ink-2)', flexShrink:0 }}>{I_SOURCES[r.s]||r.s}</span>
            <div style={{ flex:1, height:8, background:'var(--bg)', borderRadius:999, overflow:'hidden' }}><div style={{ width:(r.n/srcMax*100)+'%', height:'100%', background:'var(--brand)', borderRadius:999 }} /></div>
            <span className="tnum" style={{ width:30, textAlign:'right', fontWeight:700, fontSize:13 }}>{r.n}</span>
            <span className="tnum" style={{ width:64, textAlign:'right', fontSize:12, color:'#16965A', fontWeight:600 }}>{r.pct}% conv.</span>
          </div>))}</div>}
      </div>

      <div className="card card-pad">
        <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:4 }}>💸 Coût par lead & ROI acquisition</span>
        <div style={{ fontSize:12, color:'var(--muted)', marginBottom:14 }}>CPL, coût d'acquisition et ROAS par canal. Tague tes charges « Publicité / Marketing » par canal (Google Ads / Meta / Leboncoin) pour un calcul précis.</div>
        <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:14 }}>
          {acqKpis.map((k,i)=>(<div key={i} style={{ padding:'12px 14px', borderRadius:11, background:'var(--bg)' }}><div style={{ fontSize:10, fontWeight:700, letterSpacing:'.04em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div><div className="tnum" style={{ fontSize:20, fontWeight:800, marginTop:5, color:k.c }}>{k.v}</div></div>))}
        </div>
        {cplAlerte && <div style={{ marginTop:14, padding:'11px 14px', borderRadius:11, background:'rgba(194,54,43,.10)', border:'1px solid rgba(194,54,43,.28)', display:'flex', alignItems:'center', gap:10 }}>
          <span style={{ fontSize:18 }}>⚠️</span>
          <div style={{ fontSize:12.5, color:'var(--ink-2)', lineHeight:1.5 }}>CPL en dérapage : <b style={{ color:'#C2362B' }}>{euro(cplDernier.cpl)}</b> sur {cplDernier.label}, au-dessus de ta cible de <b>{euro(cplCible)}</b>. Réduis les enchères ou coupe les campagnes les moins rentables.</div>
        </div>}
        {(cplEvo.some(r=>r.cpl>0) || cplCible>0) && <div style={{ marginTop:16 }}>
          <div style={{ fontSize:12.5, fontWeight:600, color:'var(--ink-2)', marginBottom:8 }}>📉 Évolution du coût par lead (CPL) {cplCible>0?<span style={{ color:'var(--muted)', fontWeight:400 }}>· cible {euro(cplCible)}</span>:<span style={{ color:'var(--muted)', fontWeight:400, fontStyle:'italic' }}>· définis une cible dans Réglages pour suivre les dérapages</span>}</div>
          <ICplChart rows={cplEvo} cible={cplCible} />
        </div>}
        {cplRows.length===0 ? <div style={{ fontSize:12.5, color:'var(--muted)', marginTop:14, fontStyle:'italic' }}>Aucune donnée d'acquisition payante. Les leads avec source Google / Meta / Leboncoin et tes charges pub apparaîtront ici.</div> :
        <div style={{ overflowX:'auto', marginTop:14 }}><table className="tbl"><thead><tr>
          <th>Canal</th><th style={{ textAlign:'right' }}>Dépense</th><th style={{ textAlign:'right' }}>Leads</th><th style={{ textAlign:'right' }}>Clients</th><th style={{ textAlign:'right' }}>Conv.</th><th style={{ textAlign:'right' }}>CPL</th><th style={{ textAlign:'right' }}>Coût client</th><th style={{ textAlign:'right' }}>CA</th><th style={{ textAlign:'right' }}>ROAS</th></tr></thead>
          <tbody>{cplRows.map((r,i)=>(<tr key={i}>
            <td style={{ fontWeight:600 }}>{I_SOURCES[r.s]||r.canal}</td>
            <td className="tnum" style={{ textAlign:'right', color:'#C77A0A' }}>{r.dep?euro(r.dep):'—'}</td>
            <td className="tnum" style={{ textAlign:'right' }}>{r.leads}</td>
            <td className="tnum" style={{ textAlign:'right', color:'#16965A' }}>{r.clients}</td>
            <td className="tnum" style={{ textAlign:'right' }}>{r.conv}%</td>
            <td className="tnum" style={{ textAlign:'right', fontWeight:700, color:'#2563EB' }}>{r.cpl?euro(r.cpl):'—'}</td>
            <td className="tnum" style={{ textAlign:'right' }}>{r.cac?euro(r.cac):'—'}</td>
            <td className="tnum" style={{ textAlign:'right', fontWeight:700 }}>{euro(r.ca)}</td>
            <td className="tnum" style={{ textAlign:'right', fontWeight:800, color:r.roas>=3?'#16965A':r.roas>0?'#C77A0A':'var(--muted)' }}>{r.dep?r.roas.toFixed(1)+'×':'—'}</td></tr>))}</tbody></table></div>}
        <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:10 }}>💡 CPL = dépense ÷ leads · Coût client = dépense ÷ clients gagnés · ROAS = CA généré ÷ dépense (vise ≥ 3×). Pour des chiffres justes : crée tes leads avec la bonne source et tague tes charges pub par canal.</div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1.4fr', gap:16 }}>
        <div className="card card-pad" style={{ textAlign:'center' }}>
          <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>Taux de conversion</span>
          <div style={{ fontSize:48, fontWeight:800, color:conv>=50?'#16965A':'#B63D24' }}>{conv}%</div>
          <div style={{ fontSize:12.5, color:'var(--muted)' }}>{eng.length} acceptés · {refus.length} refusés</div>
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10, marginTop:16 }}>
            <div style={{ padding:'12px', borderRadius:10, background:'rgba(22,150,90,.10)' }}><div className="tnum" style={{ fontSize:16, fontWeight:800, color:'#16965A' }}>{euro(eng.reduce((a,m)=>a+iNum(m.montant),0))}</div><div style={{ fontSize:10.5, color:'var(--muted)' }}>CA gagné</div></div>
            <div style={{ padding:'12px', borderRadius:10, background:'rgba(194,54,43,.10)' }}><div className="tnum" style={{ fontSize:16, fontWeight:800, color:'#C2362B' }}>{euro(caPerdu)}</div><div style={{ fontSize:10.5, color:'var(--muted)' }}>CA perdu</div></div>
          </div>
        </div>
        <div className="card card-pad">
          <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:14, gap:10, flexWrap:'wrap' }}>
            <span className="section-title" style={{ fontSize:15 }}>📈 Évolution : volume & conversion</span>
            <ISelect value={evoRange} onChange={e=>setEvoRange(e.target.value)} style={{ width:'auto', height:34, fontSize:13 }}>
              {statYears.map(y=><option key={y} value={y}>Année {y}</option>)}
              <option value="all">Depuis le début</option>
            </ISelect>
          </div>
          <IEvolutionChart rows={evoRows} />
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1.3fr 1fr', gap:16 }}>
        <div className="card card-pad">
          <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>🔻 Pipeline commercial</span>
          <div style={{ display:'flex', flexDirection:'column', gap:10 }}>{pipe.map(p=>(
            <div key={p.s} style={{ display:'flex', alignItems:'center', gap:12 }}>
              <span style={{ width:130, fontSize:12.5, color:'var(--ink-2)', flexShrink:0 }}>{I_STATUS[p.s].label}</span>
              <div style={{ flex:1, height:8, background:'var(--bg)', borderRadius:999, overflow:'hidden' }}><div style={{ width:(p.n/pipeMax*100)+'%', height:'100%', background:I_STATUS[p.s].c, borderRadius:999 }} /></div>
              <span className="tnum" style={{ width:24, textAlign:'right', fontWeight:700 }}>{p.n}</span>
              <span className="tnum" style={{ width:72, textAlign:'right', fontSize:12, color:'var(--muted)' }}>{euro(p.val)}</span>
            </div>))}</div>
        </div>
        <div className="card card-pad">
          <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>📦 Revenu & rentabilité par formule</span>
          {formRows.length===0 ? <IEmpty>Aucune donnée</IEmpty> : <>
            <Donut segments={formRows.map(r=>({ label:r.label, value:r.ca, color:r.c }))} unit="€" />
            <div style={{ marginTop:14, display:'flex', flexDirection:'column', gap:9 }}>{formRows.map((r,i)=>(
              <div key={i} style={{ display:'flex', alignItems:'center', gap:8, fontSize:12.5 }}>
                <span style={{ width:9, height:9, borderRadius:'50%', background:r.c, flexShrink:0 }} />
                <span style={{ flex:1, fontWeight:600, color:'var(--ink)' }}>{r.label} <span style={{ color:'var(--muted)', fontWeight:400 }}>· {r.n} chantiers</span></span>
                <span className="tnum" style={{ color:'var(--ink-2)' }}>{euro(r.ca)}</span>
                <span className="tnum" style={{ width:48, textAlign:'right', fontWeight:700, color:r.marge>=30?'#16965A':r.marge>=0?'#C77A0A':'#C2362B' }} title="Marge nette">{r.marge}%</span>
              </div>))}</div>
          </>}
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
        <div className="card" style={{ overflow:'hidden' }}>
          <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)' }}><span className="section-title" style={{ fontSize:15 }}>⚡ Performance des chantiers</span></div>
          <div style={{ padding:'4px 18px 12px' }}>{perf.map((p,i)=>(
            <div key={i} style={{ display:'flex', justifyContent:'space-between', alignItems:'center', padding:'11px 0', borderBottom:i<perf.length-1?'1px solid var(--border)':'none' }}>
              <span style={{ fontSize:13, color:'var(--ink-2)' }}>{p.l}</span>
              <span className="tnum" style={{ fontSize:13.5, fontWeight:700, color:p.c||'var(--ink)' }}>{p.v}</span>
            </div>))}</div>
        </div>
        <div className="card card-pad">
          <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>🔄 Fidélisation & récurrence</span>
          <div style={{ display:'grid', gridTemplateColumns:'repeat(3,1fr)', gap:12 }}>
            {[['Nouveaux',nouveaux,'#2563EB'],['Fidèles',fideles,'#C77A0A'],['Premium',premiumC,'#16965A']].map(([l,v,c],i)=>(
              <div key={i} style={{ textAlign:'center', padding:'16px 8px', borderRadius:12, border:'1px solid '+c+'44', background:c+'0e' }}>
                <div className="tnum" style={{ fontSize:24, fontWeight:800, color:c }}>{v}</div>
                <div style={{ fontSize:11, color:'var(--muted)', marginTop:4 }}>{l}</div>
              </div>))}
          </div>
          <div style={{ marginTop:16 }}>
            <div style={{ display:'flex', justifyContent:'space-between', fontSize:13, marginBottom:6 }}><span style={{ color:'var(--ink-2)' }}>Taux de fidélisation</span><span className="tnum" style={{ fontWeight:800, color:tauxFidel>=20?'#16965A':'#C77A0A' }}>{tauxFidel}%</span></div>
            <div style={{ height:8, background:'var(--bg)', borderRadius:999, overflow:'hidden' }}><div style={{ width:tauxFidel+'%', height:'100%', background:'var(--brand)', borderRadius:999, transition:'width .5s' }} /></div>
            <div style={{ fontSize:11.5, color:'var(--muted)', marginTop:8 }}>{fideles} client{fideles>1?'s':''} sur {clients.length} ont fait appel à toi plusieurs fois.</div>
          </div>
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
        <div className="card" style={{ overflow:'hidden' }}>
          <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)' }}><span className="section-title" style={{ fontSize:15 }}>🏆 Top clients</span></div>
          <div style={{ padding:'8px 10px' }}>
            {topClients.length===0 ? <IEmpty>Aucun déménagement terminé</IEmpty> : topClients.map((c,i)=>(
              <div key={i} style={{ display:'flex', alignItems:'center', gap:12, padding:'10px' }}>
                <span className="tnum" style={{ width:16, color:'var(--faint)', fontSize:12.5 }}>{i+1}</span>
                <Avatar initials={iInitials(c.prenom,c.nom)} size={30} color="var(--navy)" />
                <span style={{ flex:1, fontSize:13.5, fontWeight:600, color:'var(--ink)' }}>{c.prenom} {c.nom}</span>
                <span style={{ fontSize:12, color:'var(--muted)' }}>{c.moves.length} déménag.</span>
                <span className="tnum" style={{ fontWeight:700, color:'#16965A' }}>{euro(c.ca)}</span>
              </div>))}
          </div>
        </div>
        <div className="card" style={{ overflow:'hidden' }}>
          <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
            <span className="section-title" style={{ fontSize:15 }}>🤝 Top partenaires</span>
            <span style={{ fontSize:12, color:'var(--muted)' }}>{partShare}% des leads · {euro(caPartenaires)}</span>
          </div>
          <div style={{ padding:'8px 10px' }}>
            {topPartners.length===0 ? <IEmpty>Aucun lead partenaire</IEmpty> : topPartners.map((p,i)=>(
              <div key={i} style={{ display:'flex', alignItems:'center', gap:12, padding:'10px' }}>
                <span className="tnum" style={{ width:16, color:'var(--faint)', fontSize:12.5 }}>{i+1}</span>
                <Avatar initials={iInitials(p.k.split(' ')[0],p.k.split(' ')[1])} size={30} color="var(--brand)" />
                <span style={{ flex:1, fontSize:13, fontWeight:600, color:'var(--ink)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{p.k}</span>
                <span style={{ fontSize:12, color:'var(--muted)' }}>{p.n} leads</span>
                <span className="tnum" style={{ fontWeight:700, color:'#16965A' }}>{euro(p.ca)}</span>
              </div>))}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ============================================================ CHARGES & BILAN */
function iMensualise(c){ const m=iNum(c.montant); return c.frequence==='trimestriel'?m/3:c.frequence==='annuel'?m/12:m; }
function IChargeModal({ ch, onClose, showToast }){
  const db=useInt(); const isNew=!db.charges.some(x=>x.id===ch.id);
  const [c,setC]=uIS(()=>JSON.parse(JSON.stringify(ch))); const f=(k,v)=>setC(s=>({...s,[k]:v}));
  const [facMois,setFacMois]=uIS(iMonthKey(ch.date||iToday()));
  const docs=db.documents||[];
  const contratDoc=docs.find(d=>d.chargeId===c.id&&d.kind==='contrat');
  const factures=docs.filter(d=>d.chargeId===c.id&&d.kind==='facture').sort((a,b)=>(b.mois||'').localeCompare(a.mois||''));
  const addDoc=(file,kind,mois)=>{ iCommit(db=>{ db.documents.unshift({ id:iUid('D'), nom:file.name, type:kind==='contrat'?'Contrat':'Facture', kind, mois:mois||'', chargeId:c.id, categorie:c.categorie, libelle:c.libelle, date:iToday(), taille:iSize(file.size), dataURL:file.dataURL }); }); showToast((kind==='contrat'?'Contrat lié':'Facture ajoutée')+(file.dataURL?'':' (fichier trop lourd pour l\'aperçu)')); };
  const rmDoc=(id)=>iCommit(db=>{ db.documents=db.documents.filter(d=>d.id!==id); });
  const save=()=>{ if(!iNum(c.montant)){ showToast('Montant requis'); return; } iCommit(db=>{ const i=db.charges.findIndex(x=>x.id===c.id); if(i>=0) db.charges[i]=c; else db.charges.push(c); }); showToast('Charge enregistrée'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.charges=db.charges.filter(x=>x.id!==c.id); }); showToast('Charge supprimée'); onClose(); };
  return (
    <div onMouseDown={e=>{ if(e.target===e.currentTarget) onClose(); }} style={{ position:'fixed', inset:0, zIndex:1000, background:'rgba(11,31,43,.5)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}>
      <div onMouseDown={e=>e.stopPropagation()} className="card" style={{ width:'min(520px,100%)', maxHeight:'88vh', overflow:'hidden', display:'flex', flexDirection:'column' }}>
        <div style={{ padding:'16px 20px', borderBottom:'1px solid var(--border)', display:'flex', justifyContent:'space-between', alignItems:'center', flexShrink:0 }}><span style={{ fontSize:16, fontWeight:700 }}>{isNew?'Nouvelle charge':'Modifier la charge'}</span><button onClick={onClose} style={{ display:'flex', color:'var(--muted)' }}><Icon name="x" size={18} /></button></div>
        <div className="scroll" style={{ padding:'18px 20px', overflowY:'auto' }}>
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
            <IField label="Catégorie"><ISelect value={c.categorie} onChange={e=>f('categorie',e.target.value)}>{I_CHARGE_CATS.map(k=><option key={k}>{k}</option>)}</ISelect></IField>
            <IField label="Montant (€)"><IInput type="number" value={c.montant} onChange={e=>f('montant',iNum(e.target.value))} /></IField>
          </div>
          <div style={{ marginTop:12 }}><IField label="Libellé"><IInput value={c.libelle} onChange={e=>f('libelle',e.target.value)} placeholder="Ex : loyer entrepôt, abonnement SFR…" /></IField></div>
          {c.categorie==='Publicité / Marketing' && <div style={{ marginTop:12 }}><IField label="Canal d'acquisition (pour le CPL / ROAS par canal)"><ISelect value={c.canal||''} onChange={e=>f('canal',e.target.value)}><option value="">— Choisir —</option>{['Google Ads','Meta','Leboncoin','Autre'].map(k=><option key={k}>{k}</option>)}</ISelect></IField></div>}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginTop:12 }}>
            <IField label="Date"><IInput type="date" value={c.date} onChange={e=>f('date',e.target.value)} /></IField>
            <IField label="Type"><ISelect value={c.recurrent?'1':'0'} onChange={e=>f('recurrent',e.target.value==='1')}><option value="0">Ponctuelle</option><option value="1">Récurrente</option></ISelect></IField>
          </div>
          {c.recurrent && <div style={{ marginTop:12 }}><IField label="Fréquence"><ISelect value={c.frequence} onChange={e=>f('frequence',e.target.value)}><option value="mensuel">Mensuelle</option><option value="trimestriel">Trimestrielle</option><option value="annuel">Annuelle</option></ISelect></IField></div>}

          {c.recurrent ? (<>
            <div style={{ marginTop:16, paddingTop:16, borderTop:'1px solid var(--border)' }}>
              <IField label="📄 Contrat lié (SFR, assurance, bail…)">
                {contratDoc ? <IDocChip doc={contratDoc} onRemove={()=>rmDoc(contratDoc.id)} /> : <IFileButton label="Joindre le contrat" onFile={fl=>addDoc(fl,'contrat','')} />}
              </IField>
            </div>
            <div style={{ marginTop:16 }}>
              <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:8, gap:8, flexWrap:'wrap' }}>
                <span style={{ fontSize:12.5, fontWeight:600, color:'var(--ink-2)' }}>🧾 Factures mensuelles</span>
                <div style={{ display:'flex', gap:8, alignItems:'center' }}>
                  <input type="month" value={facMois} onChange={e=>setFacMois(e.target.value)} style={{ ...inpStyle, height:32, width:'auto', fontSize:13 }} />
                  <IFileButton label="Ajouter" small onFile={fl=>addDoc(fl,'facture',facMois)} />
                </div>
              </div>
              {factures.length===0 ? <IEmpty pad={12}>Aucune facture jointe</IEmpty> : <div style={{ display:'flex', flexDirection:'column', gap:6 }}>{factures.map(d=><IDocChip key={d.id} doc={d} onRemove={()=>rmDoc(d.id)} />)}</div>}
            </div>
          </>) : (
            <div style={{ marginTop:16, paddingTop:16, borderTop:'1px solid var(--border)' }}>
              <IField label="🧾 Facture de la charge">
                {factures[0] ? <IDocChip doc={factures[0]} onRemove={()=>rmDoc(factures[0].id)} /> : <IFileButton label="Joindre la facture" onFile={fl=>addDoc(fl,'facture',iMonthKey(c.date||iToday()))} />}
              </IField>
            </div>
          )}
        </div>
        <div style={{ padding:'14px 20px', borderTop:'1px solid var(--border)', display:'flex', justifyContent:'space-between', flexShrink:0 }}>
          {!isNew?<button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}>Supprimer</button>:<span />}
          <div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>Enregistrer</button></div>
        </div>
      </div>
    </div>
  );
}
function ICharges({ showToast }){
  const db=useInt(); const [edit,setEdit]=uIS(null); const [period,setPeriod]=uIS('all');
  const recur=db.charges.filter(c=>c.recurrent); const ponct=db.charges.filter(c=>!c.recurrent);
  const commissionOf=(m)=>Math.round(iNum(m.montant)*partnerRate(m.partnerId)/100);
  const recurMensuel=recur.reduce((a,c)=>a+iMensualise(c),0);
  const storageMrr=window.LBC_ADMIN?window.LBC_ADMIN.storageAgg().mrr:0;
  const objectifCA=iNum(db.settings&&db.settings.objectifCA)||20000;
  const editObjectif=()=>{ const v=prompt('Objectif de CA mensuel (€) :', objectifCA); if(v===null) return; const n=iNum(v); if(n>0){ iCommit(db=>{ db.settings={ ...db.settings, objectifCA:n }; }); showToast('Objectif mis à jour : '+euro(n)); } };
  const now=new Date(), Y=now.getFullYear(), Mo=now.getMonth();
  const mkOf=(y,m)=>y+'-'+String(m+1).padStart(2,'0');
  const lastMonths=(n)=>{ const a=[]; for(let i=n-1;i>=0;i--){ const d=new Date(Y,Mo-i,1); a.push(mkOf(d.getFullYear(),d.getMonth())); } return a; };
  const allMonths=(()=>{ const s=new Set(); db.moves.forEach(m=>m.date&&s.add(iMonthKey(m.date))); db.charges.forEach(c=>c.date&&s.add(iMonthKey(c.date))); return [...s].sort(); })();
  const monthsFor=(p)=> /^\d{4}-\d{2}$/.test(p)?[p] : p==='month'?[mkOf(Y,Mo)] : p==='quarter'?lastMonths(3) : p==='year'?lastMonths(Mo+1) : (allMonths.length?allMonths:[mkOf(Y,Mo)]);
  const periodLabel=(p)=>({ all:'Tout', month:'Mois en cours', quarter:'3 derniers mois', year:'Année en cours' })[p]||iMonthLabel(p);
  const monthsBefore=(firstMK,n)=>{ const [y,m]=firstMK.split('-').map(Number); const a=[]; for(let i=n;i>=1;i--){ const d=new Date(y,(m-1)-i,1); a.push(mkOf(d.getFullYear(),d.getMonth())); } return a; };
  const periodMonths=monthsFor(period); const nMois=Math.max(1,periodMonths.length);
  const wonStatuts=['confirme','attente_paiement','termine'];
  const bilan=(monthsArr)=>{ const ms=Math.max(1,monthsArr.length);
    const mvs=db.moves.filter(m=>m.date&&monthsArr.includes(iMonthKey(m.date)));
    const pcts=ponct.filter(c=>c.date&&monthsArr.includes(iMonthKey(c.date)));
    const ca=mvs.reduce((a,m)=>a+iCaEncaisse(m),0);
    const benef=mvs.reduce((a,m)=>a+iBenefice(m),0);
    const fop=mvs.reduce((a,m)=>a+iCoutTotal(m),0);
    const comm=mvs.filter(m=>m.partenaire&&wonStatuts.includes(m.statut)).reduce((a,m)=>a+commissionOf(m),0);
    const fixes=recurMensuel*ms, ponctT=pcts.reduce((a,c)=>a+iNum(c.montant),0), stock=storageMrr*ms;
    const entr=ca+stock, sort=fop+fixes+ponctT+comm;
    return { mvs, pcts, ca, benef, fop, comm, fixes, ponctT, stock, entrees:entr, sorties:sort, net:entr-sort, term:mvs.filter(m=>m.statut==='termine').length };
  };
  const B=bilan(periodMonths);
  const Bp = period==='all'?null : bilan(monthsBefore(periodMonths[0], periodMonths.length));
  const delta=(c,p)=> (Bp&&p)?Math.round((c-p)/Math.abs(p)*100):null;
  const caChantiers=B.ca, benefBrut=B.benef, fraisOp=B.fop, commTotal=B.comm, chargesFixes=B.fixes, ponctTotal=B.ponctT, revenuStockage=B.stock, entrees=B.entrees, sorties=B.sorties, net=B.net, termCount=B.term;
  const marge=entrees>0?Math.round(net/entrees*100):0;
  const panierMoy=termCount?caChantiers/termCount:0;
  // trésorerie prévisionnelle (forward, toute la base)
  const aEncaisser=db.moves.filter(m=>['confirme','attente_paiement'].includes(m.statut)).reduce((a,m)=>a+Math.max(0,iNum(m.montant)-iCaEncaisse(m)),0);
  const commAVerser=db.moves.filter(m=>m.partenaire&&wonStatuts.includes(m.statut)&&!m.commissionVerse).reduce((a,m)=>a+commissionOf(m),0);
  const tresoNet=aEncaisser-commAVerser;
  // seuil de rentabilité — marge de contribution calculée sur les MÊMES chantiers (gagnés)
  const wonMoves=B.mvs.filter(m=>wonStatuts.includes(m.statut));
  const caWon=wonMoves.reduce((a,m)=>a+iNum(m.montant),0);
  const coutWon=wonMoves.reduce((a,m)=>a+iCoutTotal(m),0);
  const commWon=wonMoves.filter(m=>m.partenaire).reduce((a,m)=>a+commissionOf(m),0);
  const margeContrib=caWon>0?(caWon-coutWon-commWon)/caWon:0;
  const seuilCA=margeContrib>0?Math.round(recurMensuel/margeContrib):0;
  const panierW=wonMoves.length?caWon/wonMoves.length:0;
  const seuilN=(seuilCA>0&&panierW>0)?Math.ceil(seuilCA/panierW):0;
  const caMensuelMoy=Math.round(caWon/nMois);
  const seuilPct=seuilCA>0?Math.min(100,Math.round(caMensuelMoy/seuilCA*100)):0;
  // donut répartition des sorties (mensuel)
  const catTot={}; recur.forEach(c=>{ catTot[c.categorie]=(catTot[c.categorie]||0)+iMensualise(c); }); B.pcts.forEach(c=>{ catTot[c.categorie]=(catTot[c.categorie]||0)+iNum(c.montant)/nMois; });
  if(fraisOp) catTot['Coûts chantiers']=(catTot['Coûts chantiers']||0)+fraisOp/nMois;
  if(commTotal) catTot['Commissions partenaires']=(catTot['Commissions partenaires']||0)+commTotal/nMois;
  const I_CAT_X={ 'Coûts chantiers':'#8D6E63','Commissions partenaires':'#6D4AC4' };
  const dSegs=Object.keys(catTot).map(k=>({ label:k, value:Math.round(catTot[k]), color:I_CAT_C[k]||I_CAT_X[k]||'#999' })).sort((a,b)=>b.value-a.value);
  // barres par mois
  const mk=periodMonths;
  const caByM={}, chByM={}; mk.forEach(k=>{ caByM[k]=storageMrr; chByM[k]=recurMensuel; });
  db.moves.forEach(m=>{ const k=iMonthKey(m.date); if(k&&caByM[k]!==undefined){ caByM[k]+=iCaEncaisse(m); chByM[k]+=iCoutTotal(m); if(m.partenaire&&wonStatuts.includes(m.statut)) chByM[k]+=commissionOf(m); } });
  ponct.forEach(c=>{ const k=iMonthKey(c.date); if(k&&chByM[k]!==undefined) chByM[k]+=iNum(c.montant); });
  const mMax=Math.max(1,...mk.flatMap(k=>[caByM[k],chByM[k]]),objectifCA);
  // alertes
  const alerts=[];
  if(net<0) alerts.push({ t:'red', x:'Résultat net négatif sur la période', s:'Tes sorties dépassent tes entrées de '+euro(-net) });
  else if(marge<15) alerts.push({ t:'amber', x:'Marge faible ('+marge+'%)', s:'Surveille tes coûts chantiers et commissions.' });
  mk.forEach(k=>{ if((caByM[k]-chByM[k])<0) alerts.push({ t:'amber', x:'Mois déficitaire : '+iMonthLabel(k), s:'Net '+euro(caByM[k]-chByM[k]) }); });
  if(Bp){ const d=delta(net,Bp.net); if(d!==null&&d<0) alerts.push({ t:'amber', x:'Résultat en baisse ('+d+'%) vs période précédente', s:'Avant : '+euro(Bp.net) }); }

  const small=[ {l:'Entrées',v:euro(entrees),d:delta(entrees,Bp&&Bp.entrees)}, {l:'Sorties',v:euro(sorties),c:'#F5A0A0',inv:true,d:delta(sorties,Bp&&Bp.sorties)}, {l:'Déménag.',v:String(termCount)}, {l:'Panier moy.',v:termCount?euro(Math.round(panierMoy)):'—'} ];
  const moisTxt=nMois>1?(iK(recurMensuel)+' €/mois × '+nMois+' mois'):null;
  const cards=[ {l:'Bénéfice brut chantiers',v:euro(benefBrut),c:'#16965A'}, {l:'Revenu stockage',v:euro(revenuStockage),c:'#16965A',sub:nMois>1?(iK(storageMrr)+' €/mois × '+nMois+' mois'):null}, {l:'Coûts chantiers',v:euro(fraisOp),c:'#C77A0A'}, {l:'Commissions réseau',v:euro(commTotal),c:'#C77A0A'}, {l:'Charges fixes',v:euro(chargesFixes),c:'#C2362B',sub:moisTxt}, {l:'Charges ponctuelles',v:euro(ponctTotal),c:'#C2362B'}, {l:'Taux de marge',v:marge+'%',c:marge>=0?'#16965A':'#C2362B'} ];
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', gap:10, flexWrap:'wrap' }}>
        <ISelect value={period} onChange={e=>setPeriod(e.target.value)} style={{ width:'auto' }}>
          <option value="all">Tout</option>
          <option value="month">Mois en cours</option>
          <option value="quarter">3 derniers mois</option>
          <option value="year">Année en cours</option>
          {allMonths.length>0 && <optgroup label="Par mois">{[...allMonths].reverse().map(k=><option key={k} value={k}>{iMonthLabel(k)}</option>)}</optgroup>}
        </ISelect>
        <div style={{ display:'flex', gap:10 }}>
          <button className="btn btn-ghost btn-sm" onClick={()=>setEdit({ id:iUid('CH'), date:iToday(), categorie:'Loyer / Stockage', libelle:'', montant:0, recurrent:true, frequence:'mensuel' })}>🔁 Récurrente</button>
          <button className="btn btn-primary btn-sm" onClick={()=>setEdit({ id:iUid('CH'), date:iToday(), categorie:'Carburant', libelle:'', montant:0, recurrent:false, frequence:'mensuel' })}><Icon name="plus" size={15} />Charge ponctuelle</button>
        </div>
      </div>

      {alerts.length>0 && <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
        {alerts.slice(0,3).map((a,i)=>(<div key={i} style={{ display:'flex', alignItems:'center', gap:11, padding:'11px 15px', borderRadius:11, background:a.t==='red'?'rgba(194,54,43,.10)':'rgba(199,122,10,.10)', borderLeft:'4px solid '+(a.t==='red'?'#C2362B':'#C77A0A') }}>
          <span style={{ fontSize:16 }}>{a.t==='red'?'🔴':'⚠️'}</span>
          <div><div style={{ fontWeight:700, fontSize:13.5, color:'var(--ink)' }}>{a.x}</div><div style={{ fontSize:12, color:'var(--muted)' }}>{a.s}</div></div>
        </div>))}
      </div>}

      <div style={{ background:'linear-gradient(135deg,var(--navy-deep),var(--navy))', borderRadius:16, padding:'24px 28px', color:'#fff', display:'flex', alignItems:'center', flexWrap:'wrap', boxShadow:'var(--shadow-card)' }}>
        <div style={{ flex:1, minWidth:200 }}><div style={{ fontSize:11, fontWeight:600, letterSpacing:'.08em', textTransform:'uppercase', opacity:.6 }}>Résultat net · {periodLabel(period)}</div>
          <div className="tnum" style={{ fontSize:36, fontWeight:800, color:net>=0?'#7FDEAA':'#F5A0A0', lineHeight:1.1 }}>{euro(net)}</div>
          <div style={{ fontSize:12, opacity:.75, marginTop:5 }}>{net>=0?'✓ Activité rentable sur la période':'⚠️ Charges supérieures au CA'} · Marge {marge}%{Bp&&delta(net,Bp.net)!==null?' · '+(delta(net,Bp.net)>=0?'▲ +':'▼ ')+delta(net,Bp.net)+'% vs période précédente':''}</div></div>
        {small.map((s,i)=>(<React.Fragment key={i}><div style={{ width:1, height:54, background:'rgba(255,255,255,.15)', margin:'0 22px' }} />
          <div style={{ textAlign:'center' }}><div className="tnum" style={{ fontSize:18, fontWeight:800, color:s.c||'#fff' }}>{s.v}</div><div style={{ fontSize:10, opacity:.6, textTransform:'uppercase', letterSpacing:'.05em', marginTop:3 }}>{s.l}</div>
            {s.d!=null && <div style={{ fontSize:10, fontWeight:700, marginTop:2, color:(s.inv?s.d<=0:s.d>=0)?'#7FDEAA':'#F5A0A0' }}>{s.d>0?'▲+':s.d<0?'▼':''}{s.d}%</div>}</div></React.Fragment>))}
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit,minmax(150px,1fr))', gap:14 }}>
        {cards.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'14px 16px', display:'flex', flexDirection:'column', justifyContent:'space-between', minHeight:92 }}>
          <div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)', minHeight:28, lineHeight:1.3 }}>{k.l}</div>
          <div><div className="tnum" style={{ fontSize:20, fontWeight:800, color:k.c }}>{k.v}</div>{k.sub && <div style={{ fontSize:10, color:'var(--faint)', marginTop:2 }}>{k.sub}</div>}</div></div>))}
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:16 }}>
        <div className="card card-pad">
          <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>💧 Trésorerie prévisionnelle</span>
          <div style={{ display:'flex', justifyContent:'space-between', padding:'8px 0', fontSize:13.5 }}><span style={{ color:'var(--ink-2)' }}>Reste à encaisser (devis confirmés)</span><span className="tnum" style={{ fontWeight:700, color:'#16965A' }}>+{euro(aEncaisser)}</span></div>
          <div style={{ display:'flex', justifyContent:'space-between', padding:'8px 0', fontSize:13.5, borderBottom:'1px solid var(--border)' }}><span style={{ color:'var(--ink-2)' }}>Commissions à reverser</span><span className="tnum" style={{ fontWeight:700, color:'#C77A0A' }}>−{euro(commAVerser)}</span></div>
          <div style={{ display:'flex', justifyContent:'space-between', padding:'12px 0 0', fontSize:15 }}><span style={{ fontWeight:700 }}>Net prévisionnel</span><span className="tnum" style={{ fontWeight:800, color:tresoNet>=0?'#16965A':'#C2362B' }}>{euro(tresoNet)}</span></div>
        </div>
        <div className="card card-pad">
          <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>⚖️ Seuil de rentabilité</span>
          {seuilCA>0 ? <>
            <div style={{ fontSize:13.5, color:'var(--ink-2)', lineHeight:1.6 }}>Pour couvrir tes charges fixes (<b>{euro(Math.round(recurMensuel))}/mois</b>), il te faut <b style={{ color:'var(--brand)' }}>{euro(seuilCA)}/mois</b>{seuilN>0?<> (~<b>{seuilN}</b> déménagements)</>:''}.</div>
            <div style={{ marginTop:12, display:'flex', justifyContent:'space-between', fontSize:12, color:'var(--muted)' }}><span>CA moyen actuel : {euro(caMensuelMoy)}/mois</span><span>{seuilPct}%</span></div>
            <div style={{ height:9, background:'var(--bg)', borderRadius:999, overflow:'hidden', marginTop:6 }}><div style={{ width:seuilPct+'%', height:'100%', background:seuilPct>=100?'#16965A':'var(--brand)', borderRadius:999, transition:'width .5s' }} /></div>
            <div style={{ fontSize:11.5, marginTop:8, color:caMensuelMoy>=seuilCA?'#16965A':'#C77A0A' }}>{caMensuelMoy>=seuilCA?'✓ Seuil atteint, chaque chantier en plus est du bénéfice.':'Il manque '+euro(seuilCA-caMensuelMoy)+'/mois pour être à l\'équilibre.'}</div>
          </> : <IEmpty pad={14}>Ajoute des chantiers et des charges fixes pour calculer ton seuil.</IEmpty>}
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1.4fr 1fr', gap:16 }}>
        <div className="card card-pad"><span className="section-title" style={{ fontSize:15, display:'block', marginBottom:16 }}>Entrées vs Sorties par mois</span>
          {mk.length===0 ? <IEmpty>Aucune donnée</IEmpty> : <>
            <div style={{ position:'relative', height:166 }}>
              {[1,0.75,0.5,0.25,0].map((p,i)=>(<div key={i} style={{ position:'absolute', left:30, right:0, bottom:Math.round(p*146), borderTop:i===4?'1px solid var(--border-2)':'1px dashed var(--border)' }}>
                <span style={{ position:'absolute', left:-30, top:-7, fontSize:9, color:'var(--faint)', width:26, textAlign:'right' }}>{iK(mMax*p)}</span></div>))}
              <div style={{ position:'absolute', left:34, right:2, bottom:Math.round(objectifCA/mMax*146), borderTop:'1.5px dashed #C2992E', opacity:.9, zIndex:5 }}>
                <button onClick={editObjectif} title="Modifier l'objectif" style={{ position:'absolute', left:0, top:-11, fontSize:9.5, fontWeight:700, color:'#9A7B1E', background:'var(--card)', border:'1px solid #E4D08A', borderRadius:999, padding:'2px 9px', cursor:'pointer', display:'inline-flex', alignItems:'center', gap:4, zIndex:6 }}>🎯 Objectif {iK(objectifCA)} ✎</button></div>
              <div style={{ position:'absolute', left:34, right:2, bottom:0, top:0, display:'flex', alignItems:'flex-end', gap:16 }}>
                {mk.map(k=>(
                  <div key={k} style={{ flex:1, display:'flex', alignItems:'flex-end', justifyContent:'center', gap:6, height:146 }}>
                    <div style={{ display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'flex-end' }} title={'Entrées '+euro(caByM[k])}>
                      <span style={{ fontSize:9.5, fontWeight:700, color:'var(--navy)', marginBottom:3 }}>{iK(caByM[k])}</span>
                      <div style={{ width:18, height:Math.max(2,Math.round(caByM[k]/mMax*120)), background:'linear-gradient(180deg,#1d5170,var(--navy))', borderRadius:'5px 5px 0 0', transition:'height .55s ease' }} /></div>
                    <div style={{ display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'flex-end' }} title={'Sorties '+euro(chByM[k])}>
                      <span style={{ fontSize:9.5, fontWeight:700, color:'var(--brand)', marginBottom:3 }}>{iK(chByM[k])}</span>
                      <div style={{ width:18, height:Math.max(2,Math.round(chByM[k]/mMax*120)), background:'linear-gradient(180deg,#d8593c,var(--brand))', borderRadius:'5px 5px 0 0', transition:'height .55s ease' }} /></div>
                  </div>))}
              </div>
            </div>
            <div style={{ display:'flex', gap:16, marginTop:8, paddingLeft:34 }}>{mk.map(k=>{ const nt=caByM[k]-chByM[k]; return (
              <div key={k} style={{ flex:1, textAlign:'center' }}>
                <div style={{ fontSize:11, fontWeight:600, color:'var(--ink-2)', textTransform:'capitalize' }}>{I_MONTHS_AB[parseInt(k.slice(5,7))-1]}</div>
                <div style={{ fontSize:10.5, fontWeight:800, color:nt>=0?'#16965A':'#C2362B' }}>{nt>=0?'+':''}{iK(nt)} €</div>
              </div>); })}</div>
          </>}
          <div style={{ display:'flex', gap:16, justifyContent:'center', marginTop:12, fontSize:12 }}><span style={{ display:'inline-flex', alignItems:'center', gap:6 }}><span style={{ width:9, height:9, borderRadius:'50%', background:'var(--navy)' }} />Entrées</span><span style={{ display:'inline-flex', alignItems:'center', gap:6 }}><span style={{ width:9, height:9, borderRadius:'50%', background:'var(--brand)' }} />Sorties</span><span style={{ display:'inline-flex', alignItems:'center', gap:6, color:'#C2992E' }}>┄ Objectif CA</span></div>
        </div>
        <div className="card card-pad"><span className="section-title" style={{ fontSize:15, display:'block', marginBottom:14 }}>Répartition charges (mensuel)</span>
          {dSegs.length===0 ? <IEmpty>Aucune charge</IEmpty> : <Donut segments={dSegs} unit="€/mois" />}
        </div>
      </div>

      <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span className="section-title" style={{ fontSize:15 }}>🔁 Charges fixes & récurrentes</span>
          <span className="tnum" style={{ fontSize:12.5, fontWeight:700, color:'#C2362B', background:'rgba(194,54,43,.10)', padding:'3px 10px', borderRadius:999 }}>{euro(Math.round(recurMensuel))}/mois</span>
        </div>
        <div style={{ overflowX:'auto' }}>{recur.length===0 ? <IEmpty>Aucune charge récurrente. Ajoutez loyer, comptable, abonnements…</IEmpty> :
          <table className="tbl"><thead><tr><th>Libellé</th><th>Catégorie</th><th>Fréquence</th><th style={{ textAlign:'right' }}>Montant</th><th style={{ textAlign:'right' }}>Équiv./mois</th></tr></thead>
          <tbody>{recur.map(c=>(<tr key={c.id} onClick={()=>setEdit(c)} style={{ cursor:'pointer' }}>
            <td className="cell-client">{c.libelle}</td>
            <td><span style={{ display:'inline-flex', alignItems:'center', gap:6, fontSize:12, color:I_CAT_C[c.categorie]||'#999' }}><span style={{ width:8, height:8, borderRadius:'50%', background:I_CAT_C[c.categorie] }} />{c.categorie}</span></td>
            <td style={{ textTransform:'capitalize', color:'var(--muted)' }}>{c.frequence}</td>
            <td className="tnum" style={{ textAlign:'right', fontWeight:600 }}>{euro(iNum(c.montant))}</td>
            <td className="tnum" style={{ textAlign:'right', color:'#C2362B', fontWeight:600 }}>{euro(Math.round(iMensualise(c)))}</td></tr>))}</tbody></table>}
        </div>
      </div>

      <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span className="section-title" style={{ fontSize:15 }}>Charges ponctuelles</span>
          <span className="tnum" style={{ fontSize:13, fontWeight:700, color:'#C2362B' }}>{euro(ponctTotal)}</span></div>
        <div style={{ overflowX:'auto' }}>{B.pcts.length===0 ? <IEmpty>Aucune charge ponctuelle sur la période</IEmpty> :
          <table className="tbl"><thead><tr><th>Date</th><th>Catégorie</th><th>Libellé</th><th style={{ textAlign:'right' }}>Montant</th></tr></thead>
          <tbody>{[...B.pcts].sort((a,b)=>(b.date||'').localeCompare(a.date||'')).map(c=>(<tr key={c.id} onClick={()=>setEdit(c)} style={{ cursor:'pointer' }}>
            <td style={{ color:'var(--muted)' }}>{iFmtDate(c.date)}</td>
            <td><span style={{ display:'inline-flex', alignItems:'center', gap:6, fontSize:12, color:I_CAT_C[c.categorie]||'#999' }}><span style={{ width:8, height:8, borderRadius:'50%', background:I_CAT_C[c.categorie] }} />{c.categorie}</span></td>
            <td>{c.libelle||'—'}</td><td className="tnum" style={{ textAlign:'right', fontWeight:600, color:'#C2362B' }}>{euro(iNum(c.montant))}</td></tr>))}</tbody></table>}
        </div>
      </div>

      {edit && <IChargeModal ch={edit} onClose={()=>setEdit(null)} showToast={showToast} />}
    </div>
  );
}

/* ============================================================ DOCUMENTS */
function IDocuments({ showToast }){
  const db=useInt(); const [q,setQ]=uIS('');
  const match=(d)=>`${d.nom} ${d.categorie||''} ${d.libelle||''}`.toLowerCase().includes(q.toLowerCase());
  const docs=(db.documents||[]).filter(match);
  const contrats=docs.filter(d=>d.kind==='contrat');
  const factures=docs.filter(d=>d.kind==='facture');
  const autres=docs.filter(d=>d.kind!=='contrat'&&d.kind!=='facture');
  const byMonth={}; factures.forEach(d=>{ const k=d.mois||'0000-00'; (byMonth[k]=byMonth[k]||[]).push(d); });
  const moisKeys=Object.keys(byMonth).sort((a,b)=>b.localeCompare(a));
  const totalFact=db.documents?db.documents.filter(d=>d.kind==='facture').length:0;
  const del=(id)=>{ if(confirm('Supprimer ce document ?')){ iCommit(db=>{ db.documents=db.documents.filter(x=>x.id!==id); }); showToast('Supprimé'); } };
  const addFree=()=>{ const nom=prompt('Nom du document ?'); if(!nom) return; iCommit(db=>{ db.documents.unshift({ id:iUid('D'), nom, type:'Document', kind:'autre', date:iToday(), taille:'—' }); }); showToast('Document ajouté'); };

  const DocRow=({ d, showMonth })=>(
    <tr>
      <td><span style={{ display:'inline-flex', alignItems:'center', gap:9 }}><Icon name="fileText" size={16} style={{ color:'var(--navy)' }} /><span className="cell-client">{d.nom}</span></span></td>
      <td style={{ color:'var(--muted)', fontSize:12.5 }}>{d.categorie||d.libelle||'—'}</td>
      {showMonth && <td style={{ color:'var(--muted)', textTransform:'capitalize' }}>{d.mois?iMonthLabel(d.mois):'—'}</td>}
      <td style={{ color:'var(--muted)' }}>{iFmtDate(d.date)}</td>
      <td className="tnum" style={{ color:'var(--muted)' }}>{d.taille}</td>
      <td style={{ textAlign:'right', whiteSpace:'nowrap' }}>
        {d.dataURL && <a href={d.dataURL} download={d.nom} style={{ color:'var(--brand)', display:'inline-flex', padding:5 }} title="Télécharger"><Icon name="download" size={15} /></a>}
        <button onClick={()=>del(d.id)} style={{ color:'var(--muted)', display:'inline-flex', padding:5 }} title="Supprimer"><Icon name="x" size={15} /></button>
      </td>
    </tr>
  );

  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', flexWrap:'wrap', gap:10 }}>
        <p style={{ fontSize:13.5, color:'var(--muted)', margin:0 }}>Factures (depuis tes charges), contrats et documents. {totalFact} facture{totalFact>1?'s':''} classée{totalFact>1?'s':''} par mois.</p>
        <div style={{ display:'flex', gap:10 }}>
          <div style={{ position:'relative' }}><span style={{ position:'absolute', left:11, top:10, color:'var(--muted)' }}><Icon name="search" size={15} /></span>
            <IInput value={q} onChange={e=>setQ(e.target.value)} placeholder="Rechercher…" style={{ paddingLeft:34, height:38, width:200 }} /></div>
          <button className="btn btn-ghost btn-sm" onClick={addFree}><Icon name="plus" size={15} />Document</button>
        </div>
      </div>

      {contrats.length>0 && <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)' }}><span className="section-title" style={{ fontSize:15 }}>📄 Contrats</span></div>
        <table className="tbl"><thead><tr><th>Document</th><th>Charge liée</th><th>Date</th><th>Taille</th><th></th></tr></thead>
          <tbody>{contrats.map(d=><DocRow key={d.id} d={d} />)}</tbody></table>
      </div>}

      <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)' }}><span className="section-title" style={{ fontSize:15 }}>🧾 Factures par mois</span></div>
        {moisKeys.length===0 ? <IEmpty pad={32}>Aucune facture. Joins-les depuis une charge (récurrente ou ponctuelle).</IEmpty> :
          moisKeys.map(mkk=>{ const list=byMonth[mkk]; const tot=list.reduce((a,d)=>a+0,0); return (
            <div key={mkk}>
              <div style={{ padding:'10px 18px', background:'var(--bg)', display:'flex', justifyContent:'space-between', alignItems:'center', borderTop:'1px solid var(--border)' }}>
                <span style={{ fontSize:12.5, fontWeight:700, color:'var(--ink-2)', textTransform:'capitalize' }}>{mkk==='0000-00'?'Sans mois':iMonthLabel(mkk)}</span>
                <span style={{ fontSize:11.5, color:'var(--muted)' }}>{list.length} facture{list.length>1?'s':''}</span>
              </div>
              <table className="tbl"><tbody>{list.sort((a,b)=>(b.date||'').localeCompare(a.date||'')).map(d=><DocRow key={d.id} d={d} />)}</tbody></table>
            </div>); })}
      </div>

      {autres.length>0 && <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)' }}><span className="section-title" style={{ fontSize:15 }}>📁 Autres documents</span></div>
        <table className="tbl"><thead><tr><th>Document</th><th>Type</th><th>Date</th><th>Taille</th><th></th></tr></thead>
          <tbody>{autres.map(d=><DocRow key={d.id} d={d} />)}</tbody></table>
      </div>}
    </div>
  );
}

/* ============================================================ PARAMÈTRES */
function IParametres({ showToast }){
  const db=useInt(); const [s,setS]=uIS(()=>({ ...db.settings }));
  const f=(k,v)=>setS(x=>({...x,[k]:v}));
  const save=()=>{ iCommit(db=>{ db.settings={ ...s }; }); showToast('Paramètres enregistrés'); };
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18, maxWidth:680 }}>
      <div className="card card-pad">
        <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:16 }}>Entreprise</span>
        <p style={{ fontSize:12, color:'var(--muted)', marginTop:-8, marginBottom:14, lineHeight:1.5 }}>Ces infos apparaissent en en-tête de tous tes devis. Remplis-les avec tes vraies coordonnées.</p>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:14 }}>
          <div style={{ gridColumn:'1 / -1' }}><IField label="Nom de l'entreprise"><IInput value={s.entreprise} onChange={e=>f('entreprise',e.target.value)} /></IField></div>
          <div style={{ gridColumn:'1 / -1' }}><IField label="Ton nom & prénom (affiché dans l'app et vu par tes clients)"><IInput value={s.ownerName} onChange={e=>f('ownerName',e.target.value)} placeholder="ex : Edouard Bakirian" /></IField></div>
          <div style={{ gridColumn:'1 / -1' }}><IField label="Adresse"><IInput value={s.adresse} onChange={e=>f('adresse',e.target.value)} placeholder="N°, rue…" /></IField></div>
          <IField label="Code postal"><IInput value={s.cp} onChange={e=>f('cp',e.target.value)} placeholder="06000" /></IField>
          <IField label="Ville"><IInput value={s.ville} onChange={e=>f('ville',e.target.value)} /></IField>
          <IField label="Email"><IInput value={s.email} onChange={e=>f('email',e.target.value)} /></IField>
          <IField label="Téléphone"><IInput value={s.tel} onChange={e=>f('tel',e.target.value)} /></IField>
          <IField label="SIRET"><IInput value={s.siret} onChange={e=>f('siret',e.target.value)} /></IField>
          <IField label="N° TVA"><IInput value={s.tva} onChange={e=>f('tva',e.target.value)} /></IField>
          <div style={{ gridColumn:'1 / -1' }}><IField label="IBAN (pour les paiements)"><IInput value={s.iban} onChange={e=>f('iban',e.target.value)} placeholder="FR76…" /></IField></div>
        </div>
      </div>
      <div className="card card-pad">
        <span className="section-title" style={{ fontSize:15, display:'block', marginBottom:16 }}>Objectifs & calculs</span>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:14 }}>
          <IField label="Objectif CA mensuel (€)"><IInput type="number" value={s.objectifCA} onChange={e=>f('objectifCA',iNum(e.target.value))} /></IField>
          <IField label="Prix du carburant (€/litre)"><IInput type="number" step="0.01" value={s.prixLitre} onChange={e=>f('prixLitre',iNum(e.target.value))} /></IField>
          <IField label="Conso. par défaut (L/100km)"><IInput type="number" step="0.5" value={s.consoDefaut} onChange={e=>f('consoDefaut',iNum(e.target.value))} /></IField>
        </div>
        <p style={{ fontSize:12, color:'var(--muted)', marginTop:10, lineHeight:1.5 }}>L'essence d'un déménagement est calculée automatiquement : <b>prix au litre × conso du camion × km</b> (×2 si aller-retour). Chaque camion peut avoir sa propre conso dans sa fiche Opérationnel ; sinon la valeur par défaut s'applique.</p>
        <div style={{ height:1, background:'var(--border)', margin:'16px 0' }} />
        <div style={{ fontSize:12.5, fontWeight:700, color:'var(--ink)', marginBottom:12 }}>Tarification (prix conseillé client)</div>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:14 }}>
          <IField label="Tarif au m³ (€)"><IInput type="number" value={s.prixM3} onChange={e=>f('prixM3',iNum(e.target.value))} /></IField>
          <IField label="Tarif au km (€)"><IInput type="number" step="0.1" value={s.prixKmVente} onChange={e=>f('prixKmVente',iNum(e.target.value))} /></IField>
          <IField label="Péage camion (€/km)"><IInput type="number" step="0.01" value={s.peageKm} onChange={e=>f('peageKm',iNum(e.target.value))} /></IField>
        </div>
        <p style={{ fontSize:12, color:'var(--muted)', marginTop:10, lineHeight:1.5 }}>Le <b>prix conseillé</b> = volume × tarif m³ + distance × tarif km, ajusté selon la formule (Coup de main ×1, Mains libres ×1,2, Mains dans les poches ×1,45), avec un plancher à 1,6× le coût réel. Le <b>péage</b> est estimé au tarif camion (~0,18 €/km, classe 3/4 sur autoroute), uniquement au-delà de 60 km (gratuit en local). Soit ~165 € pour un Nice → Paris.</p>
        <div style={{ height:1, background:'var(--border)', margin:'16px 0' }} />
        <div style={{ fontSize:12.5, fontWeight:700, color:'var(--ink)', marginBottom:12 }}>Acquisition</div>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:14 }}>
          <IField label="CPL cible (€/lead)"><IInput type="number" value={s.cplCible} onChange={e=>f('cplCible',iNum(e.target.value))} placeholder="ex : 25" /></IField>
        </div>
        <p style={{ fontSize:12, color:'var(--muted)', marginTop:10, lineHeight:1.5 }}>Ton coût par lead à ne pas dépasser. Dès qu'un mois passe au-dessus, une <b>alerte rouge</b> s'affiche dans Statistiques (section acquisition) avec la courbe d'évolution du CPL. Laisse à 0 pour désactiver l'alerte.</p>
      </div>
      <div style={{ display:'flex', justifyContent:'space-between', gap:10 }}>
        <button className="btn btn-ghost btn-sm" onClick={()=>{ if(confirm('Réinitialiser toutes les données de démonstration ? (efface tes ajouts locaux)')){ iReset(); showToast('Données réinitialisées'); } }} style={{ color:'var(--perdu)' }}>Réinitialiser les données</button>
        <button className="btn btn-primary" onClick={save}><Icon name="check" size={16} />Enregistrer</button>
      </div>
    </div>
  );
}

/* ============================================================ INCIDENT MODAL */
function IIncidentModal({ data, onClose, showToast }){
  const isEdit = data && data.kind==='edit';
  const move = isEdit ? null : (data && data.move) || {};
  const [f,setF]=uIS(()=> isEdit ? JSON.parse(JSON.stringify(data.inc)) : { id:iUid('INC'), titre:'', desc:'', client:((move.prenom||'')+' '+(move.nom||'')).trim(), moveId:move.id||'', date:iToday(), createdAt:iToday(), statut:'declare', assuranceRef:'', montant:'', assuranceNote:'' });
  const [tried,setTried]=uIS(false);
  const titreErr = tried && !f.titre.trim();
  const set=(k,v)=>setF(s=>({...s,[k]:v}));
  const save=()=>{ if(!f.titre.trim()){ setTried(true); return; } iCommit(db=>{ const i=db.incidents.findIndex(x=>x.id===f.id); if(i>=0) db.incidents[i]=f; else db.incidents.unshift(f); }); showToast(isEdit?'Incident mis à jour':'Incident signalé'); onClose(); };
  const del=()=>{ iCommit(db=>{ db.incidents=db.incidents.filter(x=>x.id!==f.id); }); showToast('Incident supprimé'); onClose(); };
  const node = (
    <div onMouseDown={e=>{ if(e.target===e.currentTarget) onClose(); }} style={{ position:'fixed', inset:0, zIndex:1000, background:'rgba(11,31,43,.5)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}>
      <div onMouseDown={e=>e.stopPropagation()} className="card" style={{ width:'min(480px,100%)', maxHeight:'90vh', overflowY:'auto' }}>
        <div style={{ padding:'16px 20px', borderBottom:'1px solid var(--border)', display:'flex', justifyContent:'space-between', alignItems:'center' }}><span style={{ fontSize:16, fontWeight:700 }}>{isEdit?'Incident / litige':'Signaler un incident'}</span><button onClick={onClose} style={{ display:'flex', color:'var(--muted)' }}><Icon name="x" size={18} /></button></div>
        <div style={{ padding:'18px 20px' }}>
          {(f.moveId||f.client) && <div style={{ fontSize:13, color:'var(--muted)', marginBottom:14 }}>Déménagement {f.moveId||'—'} · {f.client||'—'}</div>}
          <IField label="Titre"><IInput value={f.titre} onChange={e=>{ set('titre',e.target.value); if(tried) setTried(false); }} placeholder="Ex : meuble endommagé" style={titreErr?{ borderColor:'#C2362B', boxShadow:'0 0 0 2px rgba(194,54,43,.12)' }:undefined} />{titreErr && <div style={{ fontSize:12, color:'#C2362B', fontWeight:600, marginTop:2 }}>⚠ Donne un titre à l'incident pour l'enregistrer.</div>}</IField>
          <div style={{ marginTop:12 }}><IField label="Description"><ITextarea value={f.desc} onChange={e=>set('desc',e.target.value)} placeholder="Ce qui s'est passé…" /></IField></div>
          <div style={{ marginTop:12 }}><IField label="Statut du litige"><ISelect value={f.statut} onChange={e=>set('statut',e.target.value)}>{Object.keys(I_INC_STATUTS).map(k=><option key={k} value={k}>{I_INC_STATUTS[k]}</option>)}</ISelect></IField></div>
          {f.statut==='assurance' && (
            <div style={{ marginTop:14, padding:'14px 16px', borderRadius:11, background:'rgba(37,99,235,.07)', border:'1px solid rgba(37,99,235,.25)' }}>
              <div style={{ fontSize:12.5, fontWeight:700, color:'#2563EB', marginBottom:10 }}>📋 Dossier assurance</div>
              <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
                <IField label="N° de déclaration"><IInput value={f.assuranceRef} onChange={e=>set('assuranceRef',e.target.value)} placeholder="ex : SIN-2026-…" /></IField>
                <IField label="Montant indemnisation (€)"><IInput type="number" value={f.montant} onChange={e=>set('montant',e.target.value)} /></IField>
              </div>
              <div style={{ marginTop:12 }}><IField label="Document / note assurance"><ITextarea value={f.assuranceNote} onChange={e=>set('assuranceNote',e.target.value)} placeholder="Lien du document, n° de dossier, contact assureur…" /></IField></div>
            </div>
          )}
          {(f.statut==='amiable') && <p style={{ fontSize:12, color:'var(--muted)', marginTop:12 }}>Réglé à l'amiable : ce litige ne sera pas comptabilisé dans les statistiques.</p>}
        </div>
        <div style={{ padding:'14px 20px', borderTop:'1px solid var(--border)', display:'flex', justifyContent:'space-between', alignItems:'center', gap:8 }}>
          {isEdit ? <button className="btn btn-ghost btn-sm" onClick={del} style={{ color:'var(--perdu)' }}>Supprimer</button> : <span />}
          <div style={{ display:'flex', gap:8 }}><button className="btn btn-ghost btn-sm" onClick={onClose}>Annuler</button><button className="btn btn-primary btn-sm" onClick={save}>{isEdit?'Enregistrer':'Signaler'}</button></div>
        </div>
      </div>
    </div>
  );
  return (window.ReactDOM && ReactDOM.createPortal) ? ReactDOM.createPortal(node, document.body) : node;
}

/* ============================================================ DEVIS PDF (impression navigateur) */
const I_DOC_CSS = ".lbc-doc{color:#17262F;font-family:Geist,Arial,sans-serif}.lbc-doc *{box-sizing:border-box}.lbc-doc h1{font-size:24px;margin:0;color:#14384E}.lbc-doc .m{color:#6B7785;font-size:13px}.lbc-doc .box{margin-top:22px}.lbc-doc table{width:100%;border-collapse:collapse;margin-top:12px}.lbc-doc td,.lbc-doc th{padding:9px 8px;text-align:left;border-bottom:1px solid #E8E3DA;font-size:13.5px}.lbc-doc th{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6B7785}.lbc-doc .tot{font-size:22px;font-weight:800;color:#B63D24}";
// échappe les données saisies (client / site web) avant injection HTML -> évite le XSS stocké
function iEsc(v){ return String(v==null?'':v).replace(/[&<>"']/g, c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
function iSideTxt(m,k){ const x=m[k]||{}; const et=iNum(x.etage); return [ [x.rue,x.cp,x.ville].filter(Boolean).map(iEsc).join(' '), et===0?'RDC':'Étage '+et, x.ascenseur?('ascenseur'+(x.ascTaille&&x.ascTaille!=='Aucun'?' '+iEsc(x.ascTaille):'')):'sans ascenseur', iNum(x.portage)?('portage '+iNum(x.portage)+' m'):'', x.notes?('accès : '+iEsc(x.notes)):'' ].filter(Boolean).join(' · '); }
function iTagList(arr){ return (arr||[]).map(t=>{ const l=iEsc((t&&t.label)||t); return (t&&t.qty&&t.qty>1)?(l+' ×'+t.qty):l; }).join(', '); }
function iDocEntete(s, right){ return `<div style="display:flex;justify-content:space-between;align-items:flex-start;border-bottom:3px solid #B63D24;padding-bottom:16px"><div><h1>${iEsc(s.entreprise||'LBC Déménagement')}</h1>${s.adresse?`<div class="m">${iEsc(s.adresse)}${(s.cp||s.ville)?', '+[s.cp,s.ville].filter(Boolean).map(iEsc).join(' '):''}</div>`:(s.ville?`<div class="m">${iEsc(s.ville)}</div>`:'')}<div class="m">${[s.tel,s.email].filter(Boolean).map(iEsc).join(' · ')}</div>${(s.siret||s.tva)?`<div class="m">${[s.siret&&('SIRET '+iEsc(s.siret)),s.tva&&('TVA '+iEsc(s.tva))].filter(Boolean).join(' · ')}</div>`:''}</div><div style="text-align:right;flex-shrink:0">${right||''}</div></div>`; }
/* ---- DEVIS (avec détail + inventaire) ---- */
function iDevisInnerHTML(m){
  const s=I_STORE.db.settings||{}; const fo=I_FORMULES[m.formule]||I_FORMULES.standard;
  const invRows=(m.inventaire||[]).map(it=>`<tr><td class="m">${iEsc(it.piece||'—')}</td><td>${iEsc(it.meuble||'—')}</td><td style="text-align:right">${iNum(it.quantite)||1}</td></tr>`).join('');
  return `<div class="lbc-doc">${iDocEntete(s, `<div style="font-size:18px;font-weight:800;color:#14384E">DEVIS</div><div class="m">N° ${iEsc(m.id)}</div><div class="m">${iFmtLong(iToday())}</div>`)}
  <div class="box"><strong>Client</strong><div class="m">${iEsc(m.prenom||'')} ${iEsc(m.nom||'')} · ${iEsc(m.tel||'')} · ${iEsc(m.email||'')}</div></div>
  <div class="box"><strong>Prestation</strong>
  <table><tr><th>Désignation</th><th>Détail</th><th style="text-align:right">Montant</th></tr>
  <tr><td>Déménagement formule ${fo.label}</td><td>${iEsc(m.dep.ville||'?')} → ${iEsc(m.arr.ville||'?')} · ${iEsc(m.volume||'?')} m³ · ${iFmtDate(m.date)}</td><td style="text-align:right">${euro(iNum(m.montant))}</td></tr></table></div>
  <div class="box"><strong>Détails du déménagement</strong><div class="m" style="margin-top:8px;line-height:1.8">
    <div><b>Formule :</b> ${fo.label} — ${fo.desc}</div>
    <div><b>Volume estimé :</b> ${iEsc(m.volume||'?')} m³ · <b>Cartons :</b> ${iEsc(m.cartons||'—')}${iNum(m.km)?` · <b>Distance :</b> ${iNum(m.km)} km`:''}</div>
    <div><b>Date :</b> ${iFmtDate(m.date)}${m.flexibilite?` (flexibilité : ${iEsc(m.flexibilite)})`:''}</div>
    <div><b>Départ :</b> ${iSideTxt(m,'dep')||'—'}</div><div><b>Arrivée :</b> ${iSideTxt(m,'arr')||'—'}</div>
    ${(m.fragiles&&m.fragiles.length)?`<div><b>Objets fragiles :</b> ${iTagList(m.fragiles)}</div>`:''}
    ${(m.demonter&&m.demonter.length)?`<div><b>À démonter / remonter :</b> ${iTagList(m.demonter)}</div>`:''}
    ${m.notes?`<div><b>Notes :</b> ${iEsc(String(m.notes)).replace(/\n/g,' · ')}</div>`:''}</div>
  ${invRows?`<table><tr><th>Pièce</th><th>Meuble</th><th style="text-align:right">Qté</th></tr>${invRows}</table>`:''}</div>
  <div style="text-align:right;margin-top:20px"><div class="m">Total TTC</div><div class="tot">${euro(iNum(m.montant))}</div>${iNum(m.acompte)>0?`<div class="m" style="margin-top:6px">Acompte : ${euro(iNum(m.acompte))}</div>`:''}</div>
  <p class="m" style="margin-top:26px">Devis valable 30 jours.${s.iban?' Règlement par virement — IBAN : '+s.iban:''}</p></div>`;
}
/* ---- FACTURE professionnelle (sans inventaire) ---- */
function iFactureInnerHTML(m){
  const s=I_STORE.db.settings||{}; const fo=I_FORMULES[m.formule]||I_FORMULES.standard;
  const ttc=iNum(m.montant); const hasTVA=!!(s.tva&&String(s.tva).trim());
  const ht=hasTVA?Math.round(ttc/1.2):ttc; const tva=ttc-ht;
  const num='FAC-'+String(m.id||'').replace(/\D/g,'');
  const totaux = hasTVA
    ? `<tr><td class="m">Sous-total HT</td><td style="text-align:right">${euro(ht)}</td></tr><tr><td class="m">TVA 20 %</td><td style="text-align:right">${euro(tva)}</td></tr><tr><td style="font-weight:800;font-size:15px;border-bottom:none">Total TTC</td><td style="text-align:right;font-weight:800;font-size:16px;color:#B63D24;border-bottom:none">${euro(ttc)}</td></tr>`
    : `<tr><td style="font-weight:800;font-size:15px;border-bottom:none">Total à payer</td><td style="text-align:right;font-weight:800;font-size:16px;color:#B63D24;border-bottom:none">${euro(ttc)}</td></tr>`;
  return `<div class="lbc-doc">${iDocEntete(s, `<div style="font-size:18px;font-weight:800;color:#14384E">FACTURE</div><div class="m">N° ${num}</div><div class="m">Émise le ${iFmtLong(iToday())}</div>`)}
  <div class="box" style="display:flex;justify-content:space-between;gap:24"><div><div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6B7785">Facturé à</div><div style="font-weight:600;margin-top:4px">${iEsc(m.prenom||'')} ${iEsc(m.nom||'')}</div><div class="m">${[m.tel,m.email].filter(Boolean).map(iEsc).join(' · ')}</div></div>
  <div style="text-align:right"><div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6B7785">Prestation</div><div class="m" style="margin-top:4px">${iEsc(m.dep.ville||'?')} → ${iEsc(m.arr.ville||'?')}</div><div class="m">${m.date?iFmtDate(m.date):''}</div></div></div>
  <table><thead><tr><th>Désignation</th><th style="text-align:right">Qté</th><th style="text-align:right">${hasTVA?'PU HT':'PU'}</th><th style="text-align:right">${hasTVA?'Total HT':'Total'}</th></tr></thead>
  <tbody><tr><td>Prestation de déménagement — formule ${fo.label}<div class="m">${iEsc(m.dep.ville||'?')} → ${iEsc(m.arr.ville||'?')}${m.volume?' · '+iEsc(m.volume)+' m³':''}</div></td><td style="text-align:right">1</td><td style="text-align:right">${euro(ht)}</td><td style="text-align:right">${euro(ht)}</td></tr></tbody></table>
  <div style="display:flex;justify-content:flex-end;margin-top:14px"><table style="width:300px">${totaux}</table></div>
  ${iNum(m.acompte)>0?`<div class="m" style="text-align:right;margin-top:4px">Acompte déjà réglé : ${euro(iNum(m.acompte))} · Reste dû : ${euro(ttc-iNum(m.acompte))}</div>`:''}
  <div style="margin-top:30px;border-top:1px solid #E8E3DA;padding-top:14px"><div class="m">Règlement à réception.${s.iban?' Par virement — IBAN : '+s.iban:''}</div>
  <div class="m" style="margin-top:4px">${hasTVA?('TVA 20 % incluse.'):'TVA non applicable, art. 293 B du CGI.'}${s.siret?' SIRET '+s.siret+'.':''}</div></div></div>`;
}
function iPrintRaw(title, inner){
  const w=window.open('','_blank','width=820,height=900');
  if(!w){ alert('Autorisez les pop-ups pour générer le PDF.'); return; }
  w.document.write(`<!doctype html><html lang="fr"><head><meta charset="utf-8"><title>${title}</title><style>body{margin:0;padding:40px}${I_DOC_CSS} .btn{margin-top:30px;background:#B63D24;color:#fff;border:0;padding:12px 20px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer}@media print{.btn{display:none}}</style></head><body>${inner}<button class="btn" onclick="window.print()">Imprimer / Enregistrer en PDF</button></body></html>`);
  w.document.close();
}
function iPrintDevis(m){ iPrintRaw('Devis '+m.id, iDevisInnerHTML(m)); }
function iPrintFacture(m){ iPrintRaw('Facture '+m.id, iFactureInnerHTML(m)); }
/* Panneau fermable pour visualiser devis/facture, avec Éditer + Imprimer */
function IDocViewer({ move, kind, onClose, onEdit }){
  const inner = kind==='facture' ? iFactureInnerHTML(move) : iDevisInnerHTML(move);
  const node = (
    <div onMouseDown={e=>{ if(e.target===e.currentTarget) onClose(); }} style={{ position:'fixed', inset:0, zIndex:1100, background:'rgba(11,31,43,.55)', display:'flex', alignItems:'center', justifyContent:'center', padding:24 }}>
      <div onMouseDown={e=>e.stopPropagation()} style={{ width:'min(720px,100%)', maxHeight:'92vh', background:'var(--card)', borderRadius:16, boxShadow:'var(--shadow-pop)', display:'flex', flexDirection:'column', overflow:'hidden' }}>
        <div style={{ padding:'12px 16px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, flexShrink:0 }}>
          <span style={{ fontSize:14, fontWeight:700, color:'var(--ink)' }}>{kind==='facture'?'Facture':'Devis'} · {move.prenom} {move.nom}</span>
          <div style={{ display:'flex', gap:8 }}>
            {onEdit && <button className="btn btn-ghost btn-sm" onClick={()=>{ onClose(); onEdit(move); }}><Icon name="settings" size={14} />Éditer</button>}
            <button className="btn btn-ghost btn-sm" onClick={()=> kind==='facture'?iPrintFacture(move):iPrintDevis(move)}><Icon name="download" size={14} />Imprimer / PDF</button>
            <button className="btn btn-primary btn-sm" onClick={onClose}>Fermer</button>
          </div>
        </div>
        <div className="scroll" style={{ flex:1, minHeight:0, overflowY:'auto', padding:'26px 30px', background:'#fff' }}>
          <style>{I_DOC_CSS}</style>
          <div dangerouslySetInnerHTML={{ __html: inner }} />
        </div>
      </div>
    </div>
  );
  return (window.ReactDOM && ReactDOM.createPortal) ? ReactDOM.createPortal(node, document.body) : node;
}

/* ============================================================ VERSEMENTS (commissions partenaires, base unique) */
function IPayouts({ showToast }){
  const db=useInt();
  const commission=(m)=>Math.round(iNum(m.montant)*partnerRate(m.partnerId)/100);
  const won=db.moves.filter(m=>m.partenaire && ['confirme','attente_paiement','termine'].includes(m.statut));
  const groups={};
  won.forEach(m=>{ const k=m.partenaire; if(!groups[k]) groups[k]={ nom:k, due:0, verse:0, dueIds:[], leads:0 };
    groups[k].leads++; const c=commission(m); if(m.commissionVerse) groups[k].verse+=c; else { groups[k].due+=c; groups[k].dueIds.push(m.id); } });
  const list=Object.values(groups).sort((a,b)=>b.due-a.due);
  const withDue=list.filter(g=>g.due>0);
  const totalDue=withDue.reduce((a,g)=>a+g.due,0);
  const totalVerse=list.reduce((a,g)=>a+g.verse,0);
  const payOne=(g)=>{ g.dueIds.forEach(id=>window.LBC_INT.setCommissionVerse(id)); showToast('Versement de '+euro(g.due)+' validé pour '+g.nom); };
  const payAll=()=>{ if(!withDue.length) return; withDue.forEach(g=>g.dueIds.forEach(id=>window.LBC_INT.setCommissionVerse(id))); showToast('Lot de versements validé · '+euro(totalDue)); };
  const kpis=[ {l:'À verser',v:euro(totalDue),c:'#C77A0A'}, {l:'Déjà versé en 2026',v:euro(totalVerse),c:'#16965A'}, {l:'Partenaires à payer',v:String(withDue.length)}, {l:'Commissions totales',v:euro(totalDue+totalVerse)} ];
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:14 }}>
        {kpis.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'14px 16px' }}><div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div><div className="tnum" style={{ fontSize:20, fontWeight:800, marginTop:6, color:k.c||'var(--ink)' }}>{k.v}</div></div>))}
      </div>
      <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'16px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, flexWrap:'wrap' }}>
          <div><span className="section-title" style={{ fontSize:15 }}>Commissions à reverser</span>
            <div style={{ fontSize:12.5, color:'var(--muted)', marginTop:3 }}>Calculées sur tes déménagements apportés par les partenaires.</div></div>
          <button className="btn btn-primary btn-sm" style={{ height:38 }} disabled={!totalDue} onClick={payAll}><Icon name="check" size={15} />Tout verser · {euro(totalDue)}</button>
        </div>
        {list.length===0 ? <IEmpty pad={40}>Aucune commission partenaire pour l'instant.</IEmpty> :
          <table className="tbl"><thead><tr><th>Partenaire</th><th style={{ textAlign:'right' }}>Leads gagnés</th><th style={{ textAlign:'right' }}>Déjà versé</th><th style={{ textAlign:'right' }}>À verser</th><th style={{ textAlign:'right' }}>Action</th></tr></thead>
          <tbody>{list.map((g,i)=>(
            <tr key={i}>
              <td><span style={{ display:'inline-flex', alignItems:'center', gap:10 }}><Avatar initials={iInitials(g.nom.split(' ')[0],g.nom.split(' ')[1])} size={30} color="var(--brand)" /><span className="cell-client">{g.nom}</span></span></td>
              <td className="tnum" style={{ textAlign:'right' }}>{g.leads}</td>
              <td className="tnum" style={{ textAlign:'right', color:'#16965A' }}>{g.verse?euro(g.verse):'—'}</td>
              <td className="tnum" style={{ textAlign:'right', fontWeight:700, color:g.due?'#C77A0A':'var(--faint)' }}>{g.due?euro(g.due):'—'}</td>
              <td style={{ textAlign:'right' }}>{g.due?<button className="btn btn-ghost btn-sm" style={{ height:32 }} onClick={()=>payOne(g)}>Verser</button>:<span style={{ fontSize:11.5, fontWeight:600, color:'#16965A' }}>✓ À jour</span>}</td>
            </tr>))}</tbody></table>}
      </div>
    </div>
  );
}

/* ============================================================ PARTENAIRES (réseau, base unique) */
function IPartenaires({ showToast }){
  const db=useInt();
  const [pending,setPending]=uIS(window.LBC_ADMIN?window.LBC_ADMIN.PENDING_PARTNERS:[]);
  const [accounts,setAccounts]=uIS(null);
  const reloadAccounts=()=>{ if(window.LBC_SB&&window.LBC_SB.listProfiles){ window.LBC_SB.listProfiles().then(rs=>setAccounts((rs||[]).filter(p=>p.role==='partenaire'))); } else setAccounts([]); };
  uIE(()=>{ reloadAccounts(); },[]);
  const activate=async(p,actif)=>{ if(!window.LBC_SB) return; await window.LBC_SB.updateProfile(p.id,{ statut:actif?'actif':'en_attente' }); showToast(actif?('Partenaire activé : '+(p.partenaire||p.email)):'Partenaire suspendu'); reloadAccounts(); if(window.LBC_SB.refreshPartnerBadge) window.LBC_SB.refreshPartnerBadge(); };
  const editSociete=async(p)=>{ if(!window.LBC_SB) return; const v=prompt('Société de ce partenaire (doit correspondre au nom utilisé sur ses leads) :', p.partenaire||''); if(v===null) return; await window.LBC_SB.updateProfile(p.id,{ partenaire:v }); showToast('Société mise à jour'); reloadAccounts(); };
  const commissionOf=(m)=>Math.round(iNum(m.montant)*partnerRate(m.partnerId)/100);
  const all=db.moves.filter(m=>m.partenaire);
  const won=all.filter(m=>['confirme','attente_paiement','termine'].includes(m.statut));
  const groups={};
  all.forEach(m=>{ const k=m.partenaire; if(!groups[k]) groups[k]={ nom:k, leads:0, gagnes:0, ca:0, verse:0, due:0 }; groups[k].leads++; groups[k].ca+=iCaEncaisse(m); });
  won.forEach(m=>{ groups[m.partenaire].gagnes++; const c=commissionOf(m); if(m.commissionVerse) groups[m.partenaire].verse+=c; else groups[m.partenaire].due+=c; });
  const list=Object.values(groups).sort((a,b)=>b.ca-a.ca);
  const caTotal=list.reduce((a,g)=>a+g.ca,0);
  const dueTotal=list.reduce((a,g)=>a+g.due,0);
  const verseTotal=list.reduce((a,g)=>a+g.verse,0);
  const kpis=[ {l:'CA via partenaires',v:euro(caTotal)}, {l:'Commissions à reverser',v:euro(dueTotal),c:'#C77A0A'}, {l:'Commissions reversées',v:euro(verseTotal),c:'#16965A'}, {l:'Partenaires actifs',v:String(accounts?accounts.filter(a=>a.statut==='actif').length:list.length)} ];
  const act=(p,ok)=>{ setPending(ps=>ps.filter(x=>x.id!==p.id)); showToast(ok?p.company+' approuvé — accès activé':'Candidature de '+p.company+' refusée'); };
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:14 }}>
        {kpis.map((k,i)=>(<div key={i} className="card card-pad" style={{ padding:'14px 16px' }}><div style={{ fontSize:10.5, fontWeight:700, letterSpacing:'.05em', textTransform:'uppercase', color:'var(--muted)' }}>{k.l}</div><div className="tnum" style={{ fontSize:20, fontWeight:800, marginTop:6, color:k.c||'var(--ink)' }}>{k.v}</div></div>))}
      </div>

      {accounts && accounts.length>0 && <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span className="section-title" style={{ fontSize:15 }}>🔐 Comptes partenaires</span>
          <span style={{ fontSize:12, color:'var(--muted)' }}>{accounts.filter(a=>a.statut==='actif').length} actif(s) · {accounts.filter(a=>a.statut!=='actif').length} en attente</span>
        </div>
        <table className="tbl"><thead><tr><th>Email</th><th>Société</th><th>Statut</th><th style={{ textAlign:'right' }}>Action</th></tr></thead>
          <tbody>{accounts.map(a=>(<tr key={a.id} style={{ cursor:'default' }}>
            <td className="cell-client">{a.email}</td>
            <td><span onClick={()=>editSociete(a)} style={{ cursor:'pointer', color:a.partenaire?'var(--ink)':'#C77A0A', fontWeight:600 }} title="Cliquer pour modifier">{a.partenaire||'À définir ✎'}</span></td>
            <td>{a.statut==='actif'?<span style={{ fontSize:12, fontWeight:700, color:'#16965A' }}>● Actif</span>:<span style={{ fontSize:12, fontWeight:700, color:'#C77A0A' }}>● En attente</span>}</td>
            <td style={{ textAlign:'right' }}>{a.statut==='actif'
              ? <button className="btn btn-ghost btn-sm" onClick={()=>activate(a,false)}>Suspendre</button>
              : <button className="btn btn-primary btn-sm" onClick={()=>activate(a,true)} disabled={!a.partenaire} title={a.partenaire?'':'Renseigne d\'abord la société'}><Icon name="check" size={14} />Activer</button>}</td>
          </tr>))}</tbody></table>
        <div style={{ padding:'10px 18px', fontSize:11.5, color:'var(--muted)' }}>Active un partenaire pour qu'il voie SES leads (ceux dont la société correspond exactement). Clique sur la société pour la corriger.</div>
      </div>}

      {pending.length>0 && <div>
        <div style={{ display:'flex', alignItems:'center', gap:9, marginBottom:10 }}><span className="section-title" style={{ fontSize:15 }}>Candidatures en attente</span><span style={{ fontSize:12, fontWeight:700, color:'#fff', background:'var(--brand)', padding:'2px 9px', borderRadius:999 }}>{pending.length}</span></div>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:14 }}>{pending.map(p=>(
          <div key={p.id} className="card card-pad" style={{ display:'flex', gap:14, alignItems:'flex-start' }}>
            <Avatar initials={p.initials} size={42} color="#C77A0A" />
            <div style={{ flex:1, minWidth:0 }}><div style={{ fontSize:15, fontWeight:700, color:'var(--ink)' }}>{p.company}</div>
              <div style={{ fontSize:12.5, color:'var(--muted)', marginTop:2 }}>{p.contact} · {p.type} · {p.city}</div>
              <div style={{ fontSize:12, color:'var(--faint)', marginTop:4 }}>{p.email} · {p.applied}</div>
              <div style={{ display:'flex', gap:9, marginTop:12 }}><button className="btn btn-primary btn-sm" onClick={()=>act(p,true)}><Icon name="check" size={14} />Approuver</button><button className="btn btn-ghost btn-sm" onClick={()=>act(p,false)}>Refuser</button></div>
            </div>
          </div>))}</div>
      </div>}

      <div className="card" style={{ overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid var(--border)' }}><span className="section-title" style={{ fontSize:15 }}>Partenaires · ce qu'ils t'apportent</span></div>
        {list.length===0 ? <IEmpty pad={40}>Aucun lead partenaire pour l'instant.</IEmpty> :
          <table className="tbl"><thead><tr><th>Partenaire</th><th style={{ textAlign:'right' }}>Leads</th><th style={{ textAlign:'right' }}>Gagnés</th><th style={{ textAlign:'right' }}>CA apporté</th><th style={{ textAlign:'right' }}>Comm. reversées</th><th style={{ textAlign:'right' }}>Comm. à reverser</th></tr></thead>
          <tbody>{list.map((g,i)=>(
            <tr key={i}>
              <td><span style={{ display:'inline-flex', alignItems:'center', gap:10 }}><Avatar initials={iInitials(g.nom.split(' ')[0],g.nom.split(' ')[1])} size={30} color="var(--brand)" /><span className="cell-client">{g.nom}</span></span></td>
              <td className="tnum" style={{ textAlign:'right' }}>{g.leads}</td>
              <td className="tnum" style={{ textAlign:'right', color:'#16965A' }}>{g.gagnes}</td>
              <td className="tnum" style={{ textAlign:'right', fontWeight:700, color:'var(--ink)' }}>{euro(g.ca)}</td>
              <td className="tnum" style={{ textAlign:'right', color:'#16965A' }}>{g.verse?euro(g.verse):'—'}</td>
              <td className="tnum" style={{ textAlign:'right', fontWeight:700, color:g.due?'#C77A0A':'var(--faint)' }}>{g.due?euro(g.due):'—'}</td>
            </tr>))}</tbody></table>}
      </div>
    </div>
  );
}

/* ============================================================ LEADS RÉSEAU (soumis par les partenaires, base réelle) */
function INetLeads(){
  const db=useInt(); const [tab,setTab]=uIS('all'); const [q,setQ]=uIS('');
  const rows=db.moves.filter(m=>m.partenaire);
  const commissionOf=(m)=>Math.round(iNum(m.montant)*partnerRate(m.partnerId)/100);
  const tabs=[['all','Tous',()=>true],['cours','En cours',m=>['devis_a_envoyer','devis_envoye'].includes(m.statut)],['acceptes','Acceptés',m=>['confirme','attente_paiement'].includes(m.statut)],['effectues','Effectués',m=>m.statut==='termine'],['perdus','Perdus',m=>['refuse','annule'].includes(m.statut)]];
  const tf=(tabs.find(t=>t[0]===tab)||tabs[0])[2];
  let list=rows.filter(tf);
  if(q) list=list.filter(m=>`${m.prenom} ${m.nom} ${m.partenaire} ${m.dep.ville} ${m.arr.ville}`.toLowerCase().includes(q.toLowerCase()));
  const del=(m)=>{ if(confirm('Supprimer ce lead ('+m.prenom+' '+m.nom+') ?')){ iCommit(db=>{ db.moves=db.moves.filter(x=>x.id!==m.id); }); } };
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:16 }}>
      <div style={{ display:'flex', justifyContent:'space-between', gap:12, flexWrap:'wrap', alignItems:'center' }}>
        <div style={{ display:'flex', gap:6, flexWrap:'wrap' }}>{tabs.map(([k,l,f])=>{ const n=rows.filter(f).length; return <button key={k} onClick={()=>setTab(k)} style={{ fontSize:13, fontWeight:600, padding:'8px 14px', borderRadius:9, border:'1px solid', borderColor:tab===k?'var(--border-2)':'transparent', background:tab===k?'var(--card)':'transparent', color:tab===k?'var(--ink)':'var(--muted)' }}>{l} <span style={{ color:'var(--muted)' }}>{n}</span></button>; })}</div>
        <div style={{ position:'relative' }}><span style={{ position:'absolute', left:11, top:10, color:'var(--muted)' }}><Icon name="search" size={15} /></span><IInput value={q} onChange={e=>setQ(e.target.value)} placeholder="Client ou partenaire…" style={{ paddingLeft:34, height:38, width:240 }} /></div>
      </div>
      <div className="card" style={{ overflow:'hidden' }}>
        {list.length===0 ? <IEmpty pad={40}>Aucun lead partenaire pour l'instant.</IEmpty> :
        <table className="tbl"><thead><tr><th>Client</th><th>Partenaire</th><th>Trajet</th><th>Date</th><th style={{ textAlign:'right' }}>Valeur</th><th>Statut</th><th style={{ textAlign:'right' }}>Commission</th><th></th></tr></thead>
        <tbody>{list.map(m=>(<tr key={m.id}>
          <td className="cell-client" onClick={()=>window.LBC_INT.openMoveGlobal&&window.LBC_INT.openMoveGlobal(m)} style={{ cursor:'pointer' }}>{m.prenom} {m.nom}</td>
          <td style={{ color:'var(--ink-2)', fontWeight:600 }}>🤝 {m.partenaire}</td>
          <td><Route from={m.dep.ville||'?'} to={m.arr.ville||'?'} /></td>
          <td style={{ color:'var(--muted)' }}>{iFmtDate(m.date||m.createdAt)}</td>
          <td className="tnum" style={{ textAlign:'right' }}>{m.montant?euro(iNum(m.montant)):'—'}</td>
          <td><IPill s={m.statut} /></td>
          <td className="tnum" style={{ textAlign:'right', fontWeight:600, color:'#16965A' }}>{['confirme','attente_paiement','termine'].includes(m.statut)?euro(commissionOf(m)):'—'}</td>
          <td style={{ textAlign:'right', whiteSpace:'nowrap' }}>
            <button onClick={(e)=>{ e.stopPropagation(); window.LBC_INT.openMoveGlobal&&window.LBC_INT.openMoveGlobal(m); }} style={{ color:'var(--muted)', display:'inline-flex', padding:5 }} title="Ouvrir"><Icon name="settings" size={15} /></button>
            <button onClick={(e)=>{ e.stopPropagation(); del(m); }} style={{ color:'var(--perdu)', display:'inline-flex', padding:5 }} title="Supprimer"><Icon name="x" size={15} /></button>
          </td>
        </tr>))}</tbody></table>}
      </div>
    </div>
  );
}

/* ============================================================ NAV + EXPORT */
const INT_NAV = [
  { group:'Gestion déménagement', items:[
    { key:'i_accueil',      label:'Accueil',          icon:'dashboard' },
    { key:'i_demenagements',label:'Déménagements',    icon:'truck' },
    { key:'i_calendrier',   label:'Calendrier',       icon:'calendar' },
    { key:'i_todo',         label:'To-do & Objectifs',icon:'check' },
    { key:'i_clients',      label:'Clients',          icon:'user' },
    { key:'i_operationnel', label:'Opérationnel',     icon:'package' },
    { key:'i_statistiques', label:'Statistiques',     icon:'list' },
    { key:'i_charges',      label:'Charges & Bilan',  icon:'euroCircle' },
    { key:'i_documents',    label:'Documents',        icon:'fileText' },
    { key:'i_parametres',   label:'Paramètres',       icon:'settings' },
  ]},
];
const INT_HEAD = {
  i_accueil:      null,
  i_demenagements:{ title:'Déménagements', sub:'Pipeline commercial et suivi des chantiers.' },
  i_calendrier:   { title:'Calendrier', sub:'RDV devis et déménagements.' },
  i_todo:         { title:'To-do & Objectifs', sub:'Matrice d\'Eisenhower et objectifs société.' },
  i_clients:      { title:'Clients', sub:'Généré automatiquement depuis les déménagements.' },
  i_operationnel: { title:'Opérationnel', sub:'Flotte, équipe et stock matériel.' },
  i_statistiques: { title:'Statistiques', sub:'Conversion, sources et performance.' },
  i_charges:      { title:'Finances & Bilan', sub:'Tout ton business dans un seul bilan : chantiers, stockage, charges et commissions.' },
  i_finances:     { title:'Finances & Bilan', sub:'Tout ton business dans un seul bilan : chantiers, stockage, charges et commissions.' },
  i_payouts:      { title:'Versements', sub:'Commissions à reverser aux partenaires, calculées sur tes déménagements.' },
  i_partners:     { title:'Partenaires', sub:'Ton réseau et ce que chaque partenaire t\'apporte.' },
  i_documents:    { title:'Documents', sub:'Modèles, CGV et fichiers.' },
  i_parametres:   { title:'Paramètres', sub:'Entreprise et préférences.' },
};
const INT_MNAV = [
  { key:'i_accueil', label:'Accueil', icon:'dashboard' },
  { key:'i_demenagements', label:'Déménag.', icon:'truck' },
  { key:'i_todo', label:'To-do', icon:'check' },
  { key:'i_clients', label:'Clients', icon:'user' },
];
const INT_MORE = ['i_calendrier','i_operationnel','i_statistiques','i_charges','i_documents','i_parametres'];

// Wrapper qui porte l'état drawers (move / incident) commun à plusieurs sections
function IntWorkspace({ screen, go, showToast }){
  const [moveDrawer,setMoveDrawer]=uIS(null);
  const [incident,setIncident]=uIS(null);
  const [doc,setDoc]=uIS(null);
  const openMove=(m)=>setMoveDrawer(m);
  const openIncident=(m)=>setIncident({ kind:'new', move:m });
  const editIncident=(inc)=>setIncident({ kind:'edit', inc });
  window.LBC_INT.openMoveGlobal=openMove;
  window.LBC_INT.openDocGlobal=(m,kind)=>setDoc({ move:m, kind:kind||'devis' });
  const sc=(screen||'').replace(/^i_/,'');
  let body;
  if(sc==='accueil') body=<IAccueil go={go} openMove={openMove} showToast={showToast} editIncident={editIncident} />;
  else if(sc==='demenagements') body=<IDemenagements openMove={openMove} showToast={showToast} openIncident={openIncident} />;
  else if(sc==='calendrier') body=<ICalendrier openMove={openMove} go={go} showToast={showToast} />;
  else if(sc==='todo') body=<ITodo showToast={showToast} go={go} />;
  else if(sc==='clients') body=<IClients openMove={openMove} />;
  else if(sc==='operationnel') body=<IOperationnel showToast={showToast} />;
  else if(sc==='statistiques') body=<IStatistiques />;
  else if(sc==='charges'||sc==='finances') body=<ICharges showToast={showToast} />;
  else if(sc==='payouts') body=<IPayouts showToast={showToast} />;
  else if(sc==='partners') body=<IPartenaires showToast={showToast} />;
  else if(sc==='net-leads') body=<INetLeads />;
  else if(sc==='documents') body=<IDocuments showToast={showToast} />;
  else if(sc==='parametres') body=<IParametres showToast={showToast} />;
  else body=<IAccueil go={go} openMove={openMove} />;
  return (<>
    {body}
    {moveDrawer && <IMoveDrawer move={moveDrawer} onClose={()=>setMoveDrawer(null)} showToast={showToast} />}
    {incident && <IIncidentModal data={incident} onClose={()=>setIncident(null)} showToast={showToast} />}
    {doc && <IDocViewer move={doc.move} kind={doc.kind} onClose={()=>setDoc(null)} onEdit={openMove} />}
  </>);
}
function IntScreen({ go, showToast, screen }){ return <IntWorkspace screen={screen} go={go} showToast={showToast} />; }

window.LBC_INT = {
  NAV:INT_NAV, HEAD:INT_HEAD, MNAV:INT_MNAV, MORE:INT_MORE,
  // composant stable par écran (sinon chaque re-render recrée le type → remonte tout → ferme les fenêtres ouvertes)
  screenFor:(screen)=>{ const C=window.LBC_INT._screenCache||(window.LBC_INT._screenCache={}); if(!C[screen]) C[screen]=(props)=> <IntWorkspace screen={screen} go={props.go} showToast={props.showToast} />; return C[screen]; },
  printDevis:iPrintDevis, printFacture:iPrintFacture, reset:iReset, openMoveGlobal:null, openDocGlobal:null,
  // --- Synchro base unique (espace partenaire ↔ cockpit) ---
  useStore:useInt, dealToLead, store:I_STORE, commit:iCommit, notify:()=>I_STORE.subs.forEach(s=>s()), iNewMove,
  flotteAlertes:()=>iToutesAlertesFlotte(), stockBas:()=>iStockBas(),
  boxNextBill:iBoxNextBill, boxEcheances:iBoxEcheances, boxFactureCumul:iBoxFactureCumul,
  validateEntretien:(camId,type)=>{ iCommit(db=>{ const c=db.camions.find(x=>x.id===camId); if(c){ const e=(c.entretiens||[]).find(x=>x.type===type); if(e){ e.dernierDate=iToday(); e.dernierKm=String(iCamionKm(camId)); e.prochainDate=''; } } }); },
  partnerLeads:(company)=> I_STORE.db.moves.filter(m=>m.partenaire===company).map(dealToLead),
  addPartnerLead:(f)=>{ const id='L-'+Math.floor(2424+Math.random()*400); const company=(f&&f.company)||(window.LBC_DATA&&window.LBC_DATA.PARTNER.company)||'';
    iCommit(db=>{ db.moves.unshift({ ...iNewMove(), id, prenom:(f&&f.first)||'', nom:(f&&f.last)||'', tel:(f&&f.phone)||'', email:(f&&f.email)||'',
      source:'partenaire', partenaire:company, partnerId:'P-01',
      formule:(f&&f.formule==='complete')?'premium':((f&&f.formule==='simple')?'eco':'standard'),
      statut:'devis_a_envoyer', montant:'', notes:(f&&f.notes)||'',
      date:(f&&f.date)?(String(f.date).length===10?f.date:frToISO(f.date)):'',
      dep:{ ...iNewSide(), ville:(f&&f.from)||'Nice' }, arr:{ ...iNewSide(), ville:(f&&f.to)||'' } }); });
    return id; },
  setMoveStatut:(id,statut)=>{ iCommit(db=>{ const m=db.moves.find(x=>x.id===id); if(m) m.statut=statut; }); },
  addBoxReservation:(b)=>{ const id='BOX-'+Math.floor(1000+Math.random()*9000); iCommit(db=>{ if(!db.boxes) db.boxes=[]; db.boxes.unshift(Object.assign({ id, createdAt:iToday(), statut:'demande' }, b)); }); return id; },
  setBoxStatut:(id,statut)=>{ iCommit(db=>{ const b=(db.boxes||[]).find(x=>x.id===id); if(b) b.statut=statut; }); },
  removeBox:(id)=>{ iCommit(db=>{ db.boxes=(db.boxes||[]).filter(x=>x.id!==id); }); },
  setCommissionVerse:(id)=>{ iCommit(db=>{ const m=db.moves.find(x=>x.id===id); if(m) m.commissionVerse=true; }); },
  // Ingestion d'un lead de devis envoyé depuis le site web (même format que le « contrat » de données)
  ingestWebLead:(p)=>{ p=p||{}; const c=p.client||{}, dep=p.depart||{}, arr=p.arrivee||{};
    const id='WEB-'+Math.floor(1000+Math.random()*9000);
    iCommit(db=>{ db.moves.unshift({ ...iNewMove(), id, extId:p._extId||'', num:id.replace(/\D/g,''), source:p.source||'site_web', partenaire:p.partenaire||'', statut:'devis_a_envoyer',
      prenom:c.prenom||'', nom:c.nom||'', tel:c.tel||'', email:c.email||'',
      formule:p.formule||'standard', formulaireType:p.formulaireType||'basique',
      volume:p.volumeEstime!=null?String(p.volumeEstime):'', cartons:p.cartons!=null?String(p.cartons):'',
      date:p.dateSouhaitee||'', notes:p.message||'',
      flexibilite:p.flexibilite||'', contactPref:p.contactPref||'',
      fragiles:Array.isArray(p.fragiles)?p.fragiles:[], demonter:Array.isArray(p.demonter)?p.demonter:[],
      inventaire:Array.isArray(p.inventaire)?p.inventaire:[],
      options:Object.assign({ demontage:false, emballage:false, gardeMeuble:false }, p.options||{}),
      dep:{ ...iNewSide(), rue:dep.adresse||'', ville:dep.ville||'', cp:dep.cp||'', etage:String(dep.etage!=null?dep.etage:0), ascenseur:!!dep.ascenseur, ascTaille:dep.ascTaille||'Aucun', portage:dep.portage!=null?String(dep.portage):'', logement:dep.logement||'appartement', notes:dep.acces||'' },
      arr:{ ...iNewSide(), rue:arr.adresse||'', ville:arr.ville||'', cp:arr.cp||'', etage:String(arr.etage!=null?arr.etage:0), ascenseur:!!arr.ascenseur, ascTaille:arr.ascTaille||'Aucun', portage:arr.portage!=null?String(arr.portage):'', logement:arr.logement||'appartement', notes:arr.acces||'' },
    }); });
    return id; },
};
