/* global React, ReactDOM, Icon, Btn, Badge, Dot, RefChip */
// DevisPage — liste / détail / builder devis Odoo (sale.order)
// Règle 0 : s'insère dans le layout design-v1 sans le modifier
// Règle 5 : CRUD complet (read, create, actions state : confirm/cancel/draft)
// Backend : GET/POST/PATCH /api/odoo/quotes · GET /api/odoo/products

(function () {
  const { useState, useEffect, useMemo, useRef } = React;

  const STATE_META = {
    draft:  { label: 'Brouillon',  tone: 'neutral', color: 'var(--ink-4)' },
    sent:   { label: 'Envoyé',     tone: 'plasma',  color: 'var(--plasma)' },
    sale:   { label: 'Confirmé',   tone: 'signal',  color: 'var(--signal-deep)' },
    done:   { label: 'Clôturé',    tone: 'signal',  color: 'var(--signal-deep)' },
    cancel: { label: 'Annulé',     tone: 'rouge',   color: 'var(--rouge)' },
  };

  const fmtEur = (n) => new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }).format(Number(n) || 0);
  const fmtEur2 = (n) => new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(Number(n) || 0);
  const fmtDate = (d) => d ? new Date(d).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' }) : '—';

  // ── Templates de lignes pré-remplies (portage DV_TEMPLATES ancienne V) ────────
  const DV_TEMPLATES = {
    solaire: {
      label: '🔆 Solaire',
      lines: [
        { designation: 'Panneaux photovoltaïques', quantity: 1, unitPrice: 0, product_name: 'panneau solaire' },
        { designation: 'Onduleur', quantity: 1, unitPrice: 0, product_name: 'onduleur' },
        { designation: 'Structure de fixation', quantity: 1, unitPrice: 0, product_name: 'fixation toiture' },
        { designation: 'Coffret AC/DC + câblage', quantity: 1, unitPrice: 0, product_name: 'coffret' },
        { designation: "Main d'œuvre installation", quantity: 1, unitPrice: 0, product_name: 'installation' },
        { designation: 'Démarches administratives (Enedis / mairie)', quantity: 1, unitPrice: 350, product_name: 'administratif' },
      ],
    },
    cee: {
      label: '♻️ CEE',
      lines: [
        { designation: 'Équipement principal', quantity: 1, unitPrice: 0, product_name: '' },
        { designation: 'Forfait constitution dossier CEE', quantity: 1, unitPrice: 450, product_name: 'CEE dossier' },
        { designation: "Main d'œuvre pose", quantity: 1, unitPrice: 0, product_name: 'main oeuvre' },
      ],
    },
    pac: {
      label: '🌡 PAC',
      lines: [
        { designation: 'PAC air/eau — fourniture', quantity: 1, unitPrice: 0, product_name: 'pompe chaleur' },
        { designation: 'Unité intérieure / hydraulique', quantity: 1, unitPrice: 0, product_name: 'unité intérieure' },
        { designation: 'Mise en service + réglages', quantity: 1, unitPrice: 600, product_name: 'mise en service' },
        { designation: "Main d'œuvre installation", quantity: 1, unitPrice: 0, product_name: 'installation' },
        { designation: 'Forfait constitution dossier CEE', quantity: 1, unitPrice: 450, product_name: 'CEE dossier' },
      ],
    },
    audit: {
      label: '📋 Audit',
      lines: [
        { designation: 'Audit énergétique réglementaire', quantity: 1, unitPrice: 1800, product_name: 'audit énergétique' },
        { designation: 'Rapport détaillé + préconisations', quantity: 1, unitPrice: 0, product_name: 'rapport' },
        { designation: 'Présentation résultats (réunion)', quantity: 1, unitPrice: 400, product_name: 'réunion' },
      ],
    },
    terrain: {
      label: '🔍 Pré-visite',
      lines: [
        { designation: 'Visite technique préalable', quantity: 1, unitPrice: 350, product_name: 'visite technique' },
        { designation: 'Relevé des données site', quantity: 1, unitPrice: 0, product_name: 'relevé' },
        { designation: 'Compte-rendu de visite', quantity: 1, unitPrice: 0, product_name: 'compte rendu' },
      ],
    },
    dim: {
      label: '📐 Dimensionnement',
      lines: [
        { designation: 'Étude de dimensionnement', quantity: 1, unitPrice: 600, product_name: 'dimensionnement' },
        { designation: 'Note de calcul', quantity: 1, unitPrice: 0, product_name: 'note calcul' },
        { designation: 'Équipement principal', quantity: 1, unitPrice: 0, product_name: '' },
      ],
    },
  };

  // Helper: convertit une ligne template vers le format Odoo (product_id/qty/price_unit)
  const tplToOdooLine = (l) => ({ id: Date.now() + Math.random(), product_id: null, product_name: l.product_name || '', name: l.designation, qty: l.quantity, price_unit: l.unitPrice });
  // Helper: convertit une ligne template vers le format AE natif (designation/quantity/unitPrice)
  const tplToAELine = (l) => ({ designation: l.designation, quantity: l.quantity, unitPrice: l.unitPrice });

  // ── TemplateBar — barre de boutons de modèles réutilisable ────────────────────
  function TemplateBar({ onSelect, active }) {
    return (
      <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginBottom: 12 }}>
        <span style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, alignSelf: 'center', marginRight: 4 }}>Modèle :</span>
        {Object.entries(DV_TEMPLATES).map(([key, tpl]) => (
          <button key={key} type="button"
            onClick={() => onSelect(key)}
            style={{
              padding: '4px 10px', fontSize: 11, borderRadius: 4, cursor: 'pointer', transition: 'all .12s',
              background: active === key ? 'var(--signal)' : 'var(--paper-2)',
              color: active === key ? 'var(--paper)' : 'var(--ink-2)',
              border: active === key ? '1px solid var(--signal-soft)' : '1px solid var(--line-2)',
              fontWeight: active === key ? 600 : 400,
            }}>{tpl.label}</button>
        ))}
      </div>
    );
  }

  // ──────────────────────────────────────────────────────────────────────────
  // DevisPage — racine : header + stats + list+detail
  // ──────────────────────────────────────────────────────────────────────────
  function DevisPage({ prefillPartnerId } = {}) {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [sourceTab, setSourceTab] = useState('ae');   // 'ae' (natif Discovery) | 'odoo'
    const [quotes, setQuotes] = useState([]);
    const [loading, setLoading] = useState(true);
    const [sel, setSel] = useState(null);
    const [q, setQ] = useState('');
    const [filterState, setFilterState] = useState('all');
    const [newOpen, setNewOpen] = useState(false);
    const [syncing, setSyncing] = useState(false);
    const [settingsOpen, setSettingsOpen] = useState(false);
    const [syncMsg, setSyncMsg] = useState(null);

    // Charger la liste
    const fetchList = () => {
      setLoading(true);
      fetch(`${API()}/api/odoo/quotes?limit=200`)
        .then(r => r.json())
        .then(d => setQuotes(d?.quotes || []))
        .catch(() => setQuotes([]))
        .finally(() => setLoading(false));
    };
    useEffect(fetchList, []);

    // Lire contexte de navigation éventuel (ex: depuis Visite/Opp)
    useEffect(() => {
      try {
        const raw = sessionStorage.getItem('ae_nav_context');
        if (raw) {
          const ctx = JSON.parse(raw);
          if (ctx?.screen === 'devis' && ctx.context?.newDevis) {
            setNewOpen(true);
            sessionStorage.removeItem('ae_nav_context');
          }
        }
      } catch (_) {}
    }, []);

    // Stats
    const stats = useMemo(() => ({
      total:     quotes.length,
      draft:     quotes.filter(q => q.state === 'draft').length,
      sent:      quotes.filter(q => q.state === 'sent').length,
      sale:      quotes.filter(q => q.state === 'sale' || q.state === 'done').length,
      amountAll: quotes.reduce((s, q) => s + (Number(q.amount_total) || 0), 0),
      amountConf: quotes.filter(q => q.state === 'sale' || q.state === 'done').reduce((s, q) => s + (Number(q.amount_total) || 0), 0),
    }), [quotes]);

    const filtered = useMemo(() => {
      let list = quotes;
      if (filterState !== 'all') list = list.filter(q => q.state === filterState);
      if (q.trim()) {
        const needle = q.trim().toLowerCase();
        list = list.filter(x => {
          const partnerName = Array.isArray(x.partner_id) ? x.partner_id[1] : '';
          return (x.name || '').toLowerCase().includes(needle)
              || String(partnerName || '').toLowerCase().includes(needle);
        });
      }
      return list;
    }, [quotes, filterState, q]);

    const doSync = async () => {
      setSyncing(true); setSyncMsg(null);
      try {
        await fetch(`${API()}/api/odoo/sync`, { method: 'POST' }).then(r => r.json());
        fetchList();
        setSyncMsg('✓ Sync terminée');
      } catch (e) { setSyncMsg(`⚠ ${e.message}`); }
      setSyncing(false);
      setTimeout(() => setSyncMsg(null), 3000);
    };

    return (
      <div style={{ padding: '16px 24px 60px' }}>
        {/* Header + tabs source */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
          <h1 style={{ fontSize: 20, fontWeight: 600, letterSpacing: '-0.02em', margin: 0 }}>Devis</h1>
          <span style={{ flex: 1 }} />
          {sourceTab === 'odoo' && <>
            <Btn variant="outline" size="sm" onClick={doSync} disabled={syncing}>
              {syncing ? '⏳ Sync…' : (syncMsg || '⟳ Sync Odoo')}
            </Btn>
            <Btn variant="signal" size="sm" icon={<Icon.plus />} onClick={() => setNewOpen(true)}>Nouveau devis Odoo</Btn>
          </>}
          <button onClick={() => setSettingsOpen(true)} title="Personnalisation devis (Règle n°6)" style={{
            padding: '5px 10px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)',
            borderRadius: 4, cursor: 'pointer', color: 'var(--ink-3)',
          }}>⚙️ Paramètres</button>
        </div>
        {settingsOpen && <DevisSettingsModal onClose={() => setSettingsOpen(false)} />}
        <div style={{ display: 'flex', gap: 4, borderBottom: '1px solid var(--line)', marginBottom: 16 }}>
          {[
            { k: 'ae',   l: '💼 Audits Énergies', sub: 'devis natifs' },
            { k: 'odoo', l: '🟣 Odoo',             sub: 'sale.order' },
          ].map(t => (
            <button key={t.k} onClick={() => { setSourceTab(t.k); setSel(null); }} style={{
              padding: '10px 16px', fontSize: 12, fontWeight: 500, cursor: 'pointer',
              color: sourceTab === t.k ? 'var(--ink)' : 'var(--ink-4)',
              borderBottom: sourceTab === t.k ? '2px solid var(--ink)' : '2px solid transparent',
              background: 'transparent', marginBottom: -1, display: 'flex', alignItems: 'baseline', gap: 6,
            }}>
              {t.l}
              <span style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 400 }}>{t.sub}</span>
            </button>
          ))}
        </div>

        {sourceTab === 'ae' && <DevisAEContent />}
        {sourceTab === 'odoo' && (<>
        {/* Stats strip */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 1, border: '1px solid var(--line)', borderRadius: 6, background: 'var(--line)', marginBottom: 16 }}>
          <Stat label="Total" value={stats.total} mono/>
          <Stat label="Brouillons" value={stats.draft} mono tone={stats.draft > 5 ? 'amber' : 'muted'}/>
          <Stat label="Envoyés" value={stats.sent} mono tone="plasma"/>
          <Stat label="Confirmés" value={stats.sale} mono tone="signal"/>
          <Stat label="Montant confirmé" value={fmtEur(stats.amountConf)}/>
        </div>

        {/* Filters */}
        <div style={{ display: 'flex', gap: 6, marginBottom: 12 }}>
          {[
            { k: 'all',    l: `Tous (${stats.total})` },
            { k: 'draft',  l: `Brouillons (${stats.draft})` },
            { k: 'sent',   l: `Envoyés (${stats.sent})` },
            { k: 'sale',   l: `Confirmés (${quotes.filter(q => q.state === 'sale').length})` },
            { k: 'cancel', l: `Annulés (${quotes.filter(q => q.state === 'cancel').length})` },
          ].map(f => (
            <button key={f.k} onClick={() => setFilterState(f.k)} style={{
              padding: '5px 10px', fontSize: 11, fontWeight: 500, cursor: 'pointer',
              background: filterState === f.k ? 'var(--ink)' : 'var(--paper)',
              color: filterState === f.k ? 'var(--paper)' : 'var(--ink-2)',
              border: '1px solid ' + (filterState === f.k ? 'var(--ink)' : 'var(--line-2)'),
              borderRadius: 4,
            }}>{f.l}</button>
          ))}
          <span style={{ flex: 1 }} />
          <input value={q} onChange={e => setQ(e.target.value)} placeholder="Rechercher devis, client…"
            style={{ padding: '5px 10px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper)', minWidth: 240 }}/>
        </div>

        {/* List + detail */}
        <div style={{ display: 'grid', gridTemplateColumns: '380px 1fr', gap: 14 }}>
          <div style={{ border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden', background: 'var(--paper-2)', maxHeight: 'calc(100vh - 320px)', display: 'flex', flexDirection: 'column' }}>
            <div style={{ flex: 1, overflowY: 'auto' }}>
              {loading && (window.Skeleton ? <div style={{ padding: 12 }}><window.Skeleton kind="rows" count={6} height={56}/></div> : <div style={{ padding: 12, fontSize: 11, color: 'var(--ink-4)' }}>Chargement…</div>)}
              {!loading && filtered.length === 0 && (window.EmptyState ? <window.EmptyState compact icon="📄" title="Aucun devis" sub="Synchronisez Odoo pour récupérer vos devis ou créez-en un depuis une opportunité." /> : <div style={{ padding: 12, fontSize: 11, color: 'var(--ink-4)' }}>Aucun devis.</div>)}
              {filtered.map(qt => {
                const meta = STATE_META[qt.state] || STATE_META.draft;
                const partnerName = Array.isArray(qt.partner_id) ? qt.partner_id[1] : '—';
                return (
                  <button key={qt.id} onClick={() => setSel(qt)} style={{
                    display: 'block', width: '100%', textAlign: 'left', padding: '10px 12px',
                    background: sel?.id === qt.id ? 'var(--paper)' : 'transparent',
                    borderLeft: sel?.id === qt.id ? '2px solid var(--signal)' : '2px solid transparent',
                    borderBottom: '1px solid var(--hairline)', cursor: 'pointer',
                  }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
                      <span className="mono" style={{ fontSize: 10, padding: '1px 5px', background: 'var(--paper-2)', borderRadius: 3, color: 'var(--ink-3)' }}>{qt.name}</span>
                      <Badge tone={meta.tone}>{meta.label}</Badge>
                      <span style={{ flex: 1 }}/>
                      <span className="mono" style={{ fontSize: 12, fontWeight: 600, color: 'var(--signal-deep)' }}>{fmtEur(qt.amount_total)}</span>
                    </div>
                    <div style={{ fontSize: 12, fontWeight: 500, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{partnerName}</div>
                    <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>{fmtDate(qt.date_order)}</div>
                  </button>
                );
              })}
            </div>
          </div>

          <div style={{ border: '1px solid var(--line)', borderRadius: 6, background: 'var(--paper-2)', padding: 20, overflow: 'auto', maxHeight: 'calc(100vh - 320px)' }}>
            {!sel && <div style={{ display: 'grid', placeItems: 'center', height: '100%', color: 'var(--ink-4)', fontSize: 12 }}>Sélectionne un devis pour voir ses lignes et agir dessus</div>}
            {sel && <DevisDetail quoteId={sel.id} onChanged={() => { fetchList(); }} onDeleted={() => { setSel(null); fetchList(); }}/>}
          </div>
        </div>

        {/* Modal création */}
        {newOpen && (
          <NewDevisModal
            prefillPartnerId={prefillPartnerId}
            onClose={() => setNewOpen(false)}
            onCreated={async (id) => {
              setNewOpen(false);
              // Re-fetch et ouvre directement le nouveau devis
              const r = await fetch(`${API()}/api/odoo/quotes?limit=300`).then(x => x.json()).catch(() => null);
              if (r?.quotes) {
                setList(r.quotes);
                const created = r.quotes.find(q => q.id === id);
                if (created) setSel(created);
              } else {
                fetchList();
              }
            }}
          />
        )}
        </>)}
      </div>
    );
  }

  // ══════════════════════════════════════════════════════════════════════════
  // DevisAE — système natif Discovery (billing/invoices?type=proforma)
  // Ancienne implémentation /api/devis + DevisAEDetail + NewDevisAEModal
  // supprimées — remplacées par DevisAEContent v2 + DevisBuilderModal ci-dessous
  // ══════════════════════════════════════════════════════════════════════════

  function _DevisAEContentDeprecated() {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [list, setList] = useState([]);
    const [loading, setLoading] = useState(true);
    const [sel, setSel] = useState(null);
    const [q, setQ] = useState('');
    const [filterState, setFilterState] = useState('all');
    const [newOpen, setNewOpen] = useState(false);

    const fetchList = () => {
      setLoading(true);
      fetch(`${API()}/api/devis?limit=300`).then(r => r.json())
        .then(d => setList(d?.devis || []))
        .catch(() => setList([]))
        .finally(() => setLoading(false));
    };
    useEffect(fetchList, []);

    const stats = useMemo(() => ({
      total:    list.length,
      draft:    list.filter(d => d.state === 'draft').length,
      sent:     list.filter(d => d.state === 'sent').length,
      accepted: list.filter(d => d.state === 'accepted').length,
      signed:   list.filter(d => d.state === 'signed').length,
      cancelled: list.filter(d => d.state === 'cancelled').length,
      amountSigned: list.filter(d => d.state === 'signed').reduce((s, d) => s + Number(d.totalTtc || 0), 0),
      amountAll:    list.reduce((s, d) => s + Number(d.totalTtc || 0), 0),
    }), [list]);

    const filtered = useMemo(() => {
      let l = list;
      if (filterState !== 'all') l = l.filter(d => d.state === filterState);
      if (q.trim()) {
        const n = q.trim().toLowerCase();
        l = l.filter(d => (d.reference || '').toLowerCase().includes(n)
          || (d.clientName || '').toLowerCase().includes(n)
          || (d.notes || '').toLowerCase().includes(n));
      }
      return l;
    }, [list, q, filterState]);

    return (
      <>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
          <span className="mono" style={{ fontSize: 11, color: 'var(--ink-4)', padding: '2px 8px', border: '1px solid var(--line)', borderRadius: 999 }}>
            {stats.total} devis · {stats.draft} brouillons · {stats.signed} signés
          </span>
          <span style={{ flex: 1 }}/>
          <Btn variant="signal" size="sm" icon={<Icon.plus />} onClick={() => setNewOpen(true)}>Nouveau devis AE</Btn>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 1, border: '1px solid var(--line)', borderRadius: 6, background: 'var(--line)', marginBottom: 16 }}>
          <Stat label="Total" value={stats.total} mono/>
          <Stat label="Brouillons" value={stats.draft} mono tone="muted"/>
          <Stat label="Envoyés" value={stats.sent} mono tone="plasma"/>
          <Stat label="Acceptés" value={stats.accepted} mono tone="signal"/>
          <Stat label="Signés" value={stats.signed} mono tone="signal"/>
          <Stat label="Montant signé" value={fmtEur(stats.amountSigned)}/>
        </div>
        <div style={{ display: 'flex', gap: 6, marginBottom: 12 }}>
          {[
            { k: 'all',       l: `Tous (${stats.total})` },
            { k: 'draft',     l: `Brouillons (${stats.draft})` },
            { k: 'sent',      l: `Envoyés (${stats.sent})` },
            { k: 'accepted',  l: `Acceptés (${stats.accepted})` },
            { k: 'signed',    l: `Signés (${stats.signed})` },
            { k: 'cancelled', l: `Annulés (${stats.cancelled})` },
          ].map(f => (
            <button key={f.k} onClick={() => setFilterState(f.k)} style={{
              padding: '5px 10px', fontSize: 11, fontWeight: 500, cursor: 'pointer',
              background: filterState === f.k ? 'var(--ink)' : 'var(--paper)',
              color: filterState === f.k ? 'var(--paper)' : 'var(--ink-2)',
              border: '1px solid ' + (filterState === f.k ? 'var(--ink)' : 'var(--line-2)'),
              borderRadius: 4,
            }}>{f.l}</button>
          ))}
          <span style={{ flex: 1 }}/>
          <input value={q} onChange={e => setQ(e.target.value)} placeholder="Rechercher devis, client…"
            style={{ padding: '5px 10px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper)', minWidth: 240 }}/>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '380px 1fr', gap: 14 }}>
          <div style={{ border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden', background: 'var(--paper-2)', maxHeight: 'calc(100vh - 320px)', display: 'flex', flexDirection: 'column' }}>
            <div style={{ flex: 1, overflowY: 'auto' }}>
              {loading && <div style={{ padding: 12, fontSize: 11, color: 'var(--ink-4)' }}>Chargement…</div>}
              {!loading && filtered.length === 0 && <div style={{ padding: 16, fontSize: 11, color: 'var(--ink-4)', textAlign: 'center' }}>Aucun devis. Créez-en un avec « Nouveau devis AE ».</div>}
              {filtered.map(d => {
                const meta = AE_STATE_META[d.state] || AE_STATE_META.draft;
                return (
                  <button key={d.id} onClick={() => setSel(d)} style={{
                    display: 'block', width: '100%', textAlign: 'left', padding: '10px 12px',
                    background: sel?.id === d.id ? 'var(--paper)' : 'transparent',
                    borderLeft: sel?.id === d.id ? '2px solid var(--signal)' : '2px solid transparent',
                    borderBottom: '1px solid var(--hairline)', cursor: 'pointer',
                  }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
                      <span className="mono" style={{ fontSize: 10, padding: '1px 5px', background: 'var(--paper-2)', borderRadius: 3, color: 'var(--ink-3)' }}>{d.reference}</span>
                      <Badge tone={meta.tone}>{meta.label}</Badge>
                      <span style={{ flex: 1 }}/>
                      <span className="mono" style={{ fontSize: 12, fontWeight: 600, color: 'var(--signal-deep)' }}>{fmtEur(d.totalTtc)}</span>
                    </div>
                    <div style={{ fontSize: 12, fontWeight: 500, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.clientName || '— Pas de client —'}</div>
                    <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>{fmtDate(d.dateOrder)} · {(d.lines || []).length} ligne{(d.lines||[]).length > 1 ? 's' : ''}</div>
                  </button>
                );
              })}
            </div>
          </div>

          <div style={{ border: '1px solid var(--line)', borderRadius: 6, background: 'var(--paper-2)', padding: 20, overflow: 'auto', maxHeight: 'calc(100vh - 320px)' }}>
            {!sel && <div style={{ display: 'grid', placeItems: 'center', height: '100%', color: 'var(--ink-4)', fontSize: 12 }}>Sélectionne un devis pour voir ses lignes et actions</div>}
            {sel && <DevisAEDetail devisId={sel.id} onChanged={fetchList} onDeleted={() => { setSel(null); fetchList(); }}/>}
          </div>
        </div>

        {newOpen && <NewDevisAEModal onClose={() => setNewOpen(false)} onCreated={async (newId) => {
          setNewOpen(false);
          // Re-fetch et ouvre directement le nouveau devis AE
          const r = await fetch(`${API()}/api/devis?limit=300`).then(x => x.json()).catch(() => null);
          if (r?.devis) {
            setList(r.devis);
            const created = r.devis.find(d => d.id === newId);
            if (created) setSel(created);
          } else {
            fetchList();
          }
        }}/>}
      </>
    );
  }

  // ──────────────────────────────────────────────────────────────────────────
  // DevisAEDetail — détail devis natif avec actions state + édition lignes
  // ──────────────────────────────────────────────────────────────────────────
  function _DevisAEDetailDeprecated({ devisId, onChanged, onDeleted }) {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [d, setD] = useState(null);
    const [loading, setLoading] = useState(true);
    const [acting, setActing] = useState(null);
    const [msg, setMsg] = useState(null);
    const [editLines, setEditLines] = useState(false);
    const [draftLines, setDraftLines] = useState([]);

    const fetchDetail = () => {
      setLoading(true);
      fetch(`${API()}/api/devis/${devisId}`).then(r => r.json())
        .then(r => { setD(r?.devis || null); setDraftLines(r?.devis?.lines || []); })
        .finally(() => setLoading(false));
    };
    useEffect(fetchDetail, [devisId]);

    const doTransition = async (path) => {
      if (path !== 'send' && !window.confirm(`Confirmer la transition « ${path} » ?`)) return;
      setActing(path); setMsg(null);
      try {
        const r = await fetch(`${API()}/api/devis/${devisId}/${path}`, { method: 'POST' }).then(r => r.json());
        if (r?.error) throw new Error(r.error);
        setMsg(`✓ ${path}`);
        fetchDetail(); onChanged && onChanged();
      } catch (e) { setMsg(`⚠ ${e.message}`); } finally { setActing(null); setTimeout(() => setMsg(null), 3000); }
    };

    const doDelete = async () => {
      if (!window.confirm(`Supprimer définitivement ${d?.reference} ?`)) return;
      setActing('delete'); setMsg(null);
      try {
        const r = await fetch(`${API()}/api/devis/${devisId}`, { method: 'DELETE' }).then(r => r.json());
        if (r?.error) throw new Error(r.error);
        onDeleted && onDeleted();
      } catch (e) { setMsg(`⚠ ${e.message}`); setActing(null); }
    };

    const saveLines = async () => {
      setActing('savelines'); setMsg(null);
      try {
        const r = await fetch(`${API()}/api/devis/${devisId}`, {
          method: 'PATCH', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ lines: draftLines }),
        }).then(r => r.json());
        if (r?.error) throw new Error(r.error);
        setEditLines(false); fetchDetail(); onChanged && onChanged();
        setMsg('✓ Lignes enregistrées');
      } catch (e) { setMsg(`⚠ ${e.message}`); } finally { setActing(null); setTimeout(() => setMsg(null), 3000); }
    };

    if (loading) return <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Chargement…</div>;
    if (!d) return <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Devis introuvable.</div>;

    const meta = AE_STATE_META[d.state] || AE_STATE_META.draft;
    const isLocked = d.state === 'signed';
    const lines = editLines ? draftLines : (d.lines || []);
    const liveTotals = useMemo(() => {
      let ht = 0, tva = 0;
      lines.forEach(l => {
        const sub = (Number(l.qty) || 0) * (Number(l.unitPrice) || 0) * (1 - (Number(l.discount) || 0) / 100);
        ht += sub; tva += sub * (Number(l.vatRate) || 0) / 100;
      });
      return { ht, tva, ttc: ht + tva };
    }, [lines]);

    const updLine = (i, patch) => setDraftLines(ls => ls.map((l, j) => j === i ? { ...l, ...patch } : l));
    const addLine = () => setDraftLines(ls => [...ls, { description: '', qty: 1, unitPrice: 0, vatRate: 20, discount: 0 }]);
    const rmLine  = (i) => setDraftLines(ls => ls.filter((_, j) => j !== i));

    return (
      <>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, borderBottom: '1px solid var(--hairline)', paddingBottom: 12, marginBottom: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 18, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 8 }}>
              📄 {d.reference}
              <Badge tone={meta.tone}>{meta.label}</Badge>
            </div>
            <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>{d.clientName || '—'} · {fmtDate(d.dateOrder)} · validité {fmtDate(d.validityDate)}</div>
          </div>
          {!isLocked && (
            <div style={{ display: 'flex', gap: 5 }}>
              {d.state === 'draft' && <button onClick={() => doTransition('send')} disabled={!!acting} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--plasma)', color: '#fff', border: 'none', borderRadius: 4, fontWeight: 600, cursor: 'pointer' }}>📤 Envoyer</button>}
              {(d.state === 'sent' || d.state === 'draft') && <button onClick={() => doTransition('accept')} disabled={!!acting} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 4, fontWeight: 600, cursor: 'pointer' }}>✓ Accepté</button>}
              {(d.state === 'accepted' || d.state === 'sent') && <button onClick={() => doTransition('sign')} disabled={!!acting} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--signal-deep)', color: '#fff', border: 'none', borderRadius: 4, fontWeight: 600, cursor: 'pointer' }}>✍ Signer</button>}
              {(d.state === 'sent' || d.state === 'cancelled') && <button onClick={() => doTransition('draft')} disabled={!!acting} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>↺ Brouillon</button>}
              {d.state !== 'cancelled' && <button onClick={() => doTransition('cancel')} disabled={!!acting} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 4, cursor: 'pointer' }}>⊘ Annuler</button>}
              <a href={`${API()}/api/devis/${d.id}/pdf`} target="_blank" rel="noopener" title="Télécharger PDF" style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper)', color: 'var(--ink-2)', border: '1px solid var(--line-2)', borderRadius: 4, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }}>📄 PDF</a>
              <button onClick={doDelete} disabled={!!acting} title="Supprimer" style={{ padding: '5px 10px', fontSize: 11, background: 'transparent', color: 'var(--ink-4)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>🗑</button>
            </div>
          )}
        </div>

        {msg && <div style={{ marginBottom: 10, padding: '6px 10px', fontSize: 11, background: msg.startsWith('⚠') ? 'var(--rouge-tint)' : 'var(--signal-tint)', border: '1px solid ' + (msg.startsWith('⚠') ? 'var(--rouge)' : 'var(--signal-soft)'), borderRadius: 5 }}>{msg}</div>}

        {/* Totaux */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10, marginBottom: 16 }}>
          <div><div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>Total HT</div><div className="mono" style={{ fontSize: 16, fontWeight: 600 }}>{fmtEur2(liveTotals.ht)}</div></div>
          <div><div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>TVA</div><div className="mono" style={{ fontSize: 16, fontWeight: 600 }}>{fmtEur2(liveTotals.tva)}</div></div>
          <div><div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>Total TTC</div><div className="mono" style={{ fontSize: 16, fontWeight: 600, color: 'var(--signal-deep)' }}>{fmtEur2(liveTotals.ttc)}</div></div>
        </div>

        {/* Lignes */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
          <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600 }}>Lignes ({lines.length})</div>
          <span style={{ flex: 1 }}/>
          {!isLocked && !editLines && <button onClick={() => setEditLines(true)} style={{ padding: '3px 8px', fontSize: 10, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3 }}>✏️ Éditer</button>}
          {editLines && <>
            <button onClick={addLine} style={{ padding: '3px 8px', fontSize: 10, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3 }}>+ Ligne</button>
            <button onClick={() => { setEditLines(false); setDraftLines(d.lines || []); }} style={{ padding: '3px 8px', fontSize: 10, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3 }}>Annuler</button>
            <button onClick={saveLines} disabled={acting === 'savelines'} style={{ padding: '3px 10px', fontSize: 10, background: 'var(--signal)', border: 'none', borderRadius: 3, fontWeight: 600 }}>{acting === 'savelines' ? '…' : '✓ Enregistrer'}</button>
          </>}
        </div>
        <div style={{ border: '1px solid var(--line)', borderRadius: 5, overflow: 'hidden', marginBottom: 16 }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 60px 90px 60px 60px 90px 24px', padding: '8px 10px', background: 'var(--paper)', fontSize: 10, fontWeight: 600, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4, borderBottom: '1px solid var(--hairline)' }}>
            <div>Désignation</div><div style={{ textAlign: 'right' }}>Qté</div><div style={{ textAlign: 'right' }}>PU HT</div><div style={{ textAlign: 'right' }}>TVA%</div><div style={{ textAlign: 'right' }}>Rem%</div><div style={{ textAlign: 'right' }}>Total HT</div><div/>
          </div>
          {lines.length === 0 && <div style={{ padding: 10, fontSize: 11, color: 'var(--ink-4)' }}>Aucune ligne.</div>}
          {lines.map((l, i) => {
            const sub = (Number(l.qty) || 0) * (Number(l.unitPrice) || 0) * (1 - (Number(l.discount) || 0) / 100);
            const inp = { padding: '3px 6px', fontSize: 11, border: '1px solid var(--line-2)', borderRadius: 3, background: 'var(--paper-2)', textAlign: 'right', boxSizing: 'border-box', width: '100%' };
            return (
              <div key={i} style={{ display: 'grid', gridTemplateColumns: '1fr 60px 90px 60px 60px 90px 24px', gap: 4, padding: '6px 10px', fontSize: 12, borderBottom: '1px solid var(--hairline)', alignItems: 'center', background: 'var(--paper-2)' }}>
                {editLines
                  ? <input value={l.description || ''} onChange={e => updLine(i, { description: e.target.value })} placeholder="Désignation…" style={{ ...inp, textAlign: 'left' }}/>
                  : <div>{l.description || '—'}</div>}
                {editLines
                  ? <input type="number" step="0.01" value={l.qty || ''} onChange={e => updLine(i, { qty: Number(e.target.value) })} style={inp}/>
                  : <div className="mono" style={{ textAlign: 'right' }}>{l.qty}</div>}
                {editLines
                  ? <input type="number" step="0.01" value={l.unitPrice || ''} onChange={e => updLine(i, { unitPrice: Number(e.target.value) })} style={inp}/>
                  : <div className="mono" style={{ textAlign: 'right' }}>{fmtEur2(l.unitPrice)}</div>}
                {editLines
                  ? <input type="number" step="0.01" value={l.vatRate || ''} onChange={e => updLine(i, { vatRate: Number(e.target.value) })} style={inp}/>
                  : <div className="mono" style={{ textAlign: 'right' }}>{l.vatRate}%</div>}
                {editLines
                  ? <input type="number" step="0.01" value={l.discount || ''} onChange={e => updLine(i, { discount: Number(e.target.value) })} style={inp}/>
                  : <div className="mono" style={{ textAlign: 'right' }}>{l.discount || 0}%</div>}
                <div className="mono" style={{ textAlign: 'right', fontWeight: 600 }}>{fmtEur2(sub)}</div>
                {editLines ? <button onClick={() => rmLine(i)} title="Supprimer" style={{ padding: 0, fontSize: 12, background: 'transparent', color: 'var(--rouge)', border: 'none', cursor: 'pointer' }}>×</button> : <div/>}
              </div>
            );
          })}
        </div>

        {d.notes && <div style={{ padding: 10, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 5, fontSize: 12, marginBottom: 12, whiteSpace: 'pre-wrap' }}>
          <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Notes</div>
          {d.notes}
        </div>}

        {/* Chatter natif */}
        {window.ChatterPanel && <window.ChatterPanel entityType="devis_ae" entityId={d.id}/>}
      </>
    );
  }

  // ──────────────────────────────────────────────────────────────────────────
  // NewDevisAEModal — DÉPRÉCIÉ (remplacé par DevisBuilderModal)
  // ──────────────────────────────────────────────────────────────────────────
  function _NewDevisAEModalDeprecated({ onClose, onCreated }) {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [pickedClient, setPickedClient] = useState(null);
    const [pickerOpen, setPickerOpen] = useState(false);
    const [validityDate, setValidityDate] = useState('');
    const [notes, setNotes] = useState('');
    const [lines, setLines] = useState([{ description: '', qty: 1, unitPrice: 0, vatRate: 20, discount: 0 }]);
    const [productQuery, setProductQuery] = useState('');
    const [productResults, setProductResults] = useState([]);
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState(null);

    // Search produits catalogue Discovery (EPREL + seeds + manuels)
    useEffect(() => {
      if (!productQuery.trim()) { setProductResults([]); return; }
      const t = setTimeout(() => {
        fetch(`${API()}/api/products?q=${encodeURIComponent(productQuery)}&limit=8`).then(r => r.json())
          .then(d => setProductResults(d?.products || d || []))
          .catch(() => setProductResults([]));
      }, 250);
      return () => clearTimeout(t);
    }, [productQuery]);

    const totals = useMemo(() => {
      let ht = 0, tva = 0;
      lines.forEach(l => {
        const sub = (Number(l.qty) || 0) * (Number(l.unitPrice) || 0) * (1 - (Number(l.discount) || 0) / 100);
        ht += sub; tva += sub * (Number(l.vatRate) || 0) / 100;
      });
      return { ht, tva, ttc: ht + tva };
    }, [lines]);

    const addLine = (preset) => setLines(ls => [...ls, preset || { description: '', qty: 1, unitPrice: 0, vatRate: 20, discount: 0 }]);
    const updLine = (i, patch) => setLines(ls => ls.map((l, j) => j === i ? { ...l, ...patch } : l));
    const rmLine  = (i) => setLines(ls => ls.filter((_, j) => j !== i));

    const useProduct = (p) => {
      addLine({ productId: p.id, description: `${p.brand || ''} ${p.model || p.name || ''}`.trim() || p.name, qty: 1, unitPrice: Number(p.price_ht || p.list_price || 0), vatRate: 20, discount: 0 });
      setProductQuery(''); setProductResults([]);
    };

    const submit = async () => {
      setBusy(true); setError(null);
      try {
        const body = {
          contactId: pickedClient?.source !== 'odoo' ? pickedClient?.id : null,
          organizationId: pickedClient?.organizationId || null,
          clientName: pickedClient?.name || null,
          clientEmail: pickedClient?.email || null,
          validityDate: validityDate || null,
          notes: notes || null,
          lines,
        };
        const r = await fetch(`${API()}/api/devis`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json());
        if (r?.error) throw new Error(r.error);
        onCreated && onCreated(r.devis?.id);
      } catch (e) { setError(e.message); } finally { setBusy(false); }
    };

    const inp = { padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper)', boxSizing: 'border-box', width: '100%' };

    return (
      <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.45)', zIndex: 1000, display: 'grid', placeItems: 'center' }}>
        <div style={{ background: 'var(--paper-2)', border: '1px solid var(--line)', borderRadius: 8, padding: 20, minWidth: 720, maxWidth: 900, maxHeight: '90vh', overflow: 'auto', boxShadow: 'var(--sh)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16, paddingBottom: 12, borderBottom: '1px solid var(--hairline)' }}>
            <div style={{ fontSize: 16, fontWeight: 700 }}>📄 Nouveau devis Audits Énergies</div>
            <span style={{ flex: 1 }}/>
            <button onClick={onClose} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4 }}>×</button>
          </div>

          {/* Client */}
          <div style={{ marginBottom: 12 }}>
            <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Client</div>
            {pickedClient
              ? <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 5 }}>
                  <span style={{ fontSize: 12, fontWeight: 600 }}>{pickedClient.name}</span>
                  <span style={{ fontSize: 10, color: 'var(--ink-4)' }}>{pickedClient.email || ''} · {pickedClient.source === 'odoo' ? 'Odoo' : 'AE'} #{pickedClient.id}</span>
                  <span style={{ flex: 1 }}/>
                  <button onClick={() => setPickedClient(null)} style={{ fontSize: 11, padding: '3px 8px', background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 3 }}>Changer</button>
                </div>
              : window.ClientPicker
                ? <window.ClientPicker onSelect={setPickedClient} />
                : <input placeholder="Nom client (libre)" onChange={e => setPickedClient({ name: e.target.value })} style={inp}/>}
          </div>

          {/* Date validité + recherche produit */}
          <div style={{ display: 'grid', gridTemplateColumns: '180px 1fr', gap: 10, marginBottom: 12 }}>
            <div>
              <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Validité</div>
              <input type="date" value={validityDate} onChange={e => setValidityDate(e.target.value)} style={inp}/>
            </div>
            <div style={{ position: 'relative' }}>
              <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Ajouter un produit du catalogue</div>
              <input value={productQuery} onChange={e => setProductQuery(e.target.value)} placeholder="Rechercher PAC, panneau, batterie…" style={inp}/>
              {productResults.length > 0 && (
                <div style={{ position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 2, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 4, zIndex: 10, maxHeight: 220, overflowY: 'auto', boxShadow: 'var(--sh)' }}>
                  {productResults.map(p => (
                    <button key={p.id} onClick={() => useProduct(p)} style={{ display: 'block', width: '100%', textAlign: 'left', padding: '6px 10px', fontSize: 11, borderBottom: '1px solid var(--hairline)', background: 'transparent', cursor: 'pointer' }}>
                      <div style={{ fontWeight: 500 }}>{p.brand || ''} {p.model || p.name}</div>
                      <div style={{ fontSize: 9, color: 'var(--ink-4)' }}>{p.category || p.sub_category || ''} · {fmtEur2(p.price_ht || p.list_price || 0)}</div>
                    </button>
                  ))}
                </div>
              )}
            </div>
          </div>

          {/* Lignes */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600 }}>Lignes ({lines.length})</div>
            <span style={{ flex: 1 }}/>
            <button onClick={() => addLine()} style={{ padding: '3px 8px', fontSize: 10, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3 }}>+ Ligne libre</button>
          </div>
          <div style={{ border: '1px solid var(--line)', borderRadius: 5, overflow: 'hidden', marginBottom: 12 }}>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 60px 90px 60px 60px 90px 24px', padding: '6px 8px', background: 'var(--paper)', fontSize: 10, fontWeight: 600, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4 }}>
              <div>Désignation</div><div style={{ textAlign: 'right' }}>Qté</div><div style={{ textAlign: 'right' }}>PU HT</div><div style={{ textAlign: 'right' }}>TVA%</div><div style={{ textAlign: 'right' }}>Rem%</div><div style={{ textAlign: 'right' }}>Total HT</div><div/>
            </div>
            {lines.map((l, i) => {
              const sub = (Number(l.qty) || 0) * (Number(l.unitPrice) || 0) * (1 - (Number(l.discount) || 0) / 100);
              const cell = { padding: '4px 6px', fontSize: 11, border: '1px solid var(--line-2)', borderRadius: 3, background: 'var(--paper-2)', textAlign: 'right', boxSizing: 'border-box', width: '100%' };
              return (
                <div key={i} style={{ display: 'grid', gridTemplateColumns: '1fr 60px 90px 60px 60px 90px 24px', gap: 4, padding: '6px 8px', borderTop: '1px solid var(--hairline)', alignItems: 'center' }}>
                  <input value={l.description || ''} onChange={e => updLine(i, { description: e.target.value })} placeholder="Désignation…" style={{ ...cell, textAlign: 'left' }}/>
                  <input type="number" step="0.01" value={l.qty || ''} onChange={e => updLine(i, { qty: Number(e.target.value) })} style={cell}/>
                  <input type="number" step="0.01" value={l.unitPrice || ''} onChange={e => updLine(i, { unitPrice: Number(e.target.value) })} style={cell}/>
                  <input type="number" step="0.01" value={l.vatRate || ''} onChange={e => updLine(i, { vatRate: Number(e.target.value) })} style={cell}/>
                  <input type="number" step="0.01" value={l.discount || ''} onChange={e => updLine(i, { discount: Number(e.target.value) })} style={cell}/>
                  <div className="mono" style={{ textAlign: 'right', fontWeight: 600, fontSize: 11 }}>{fmtEur2(sub)}</div>
                  <button onClick={() => rmLine(i)} style={{ padding: 0, fontSize: 14, background: 'transparent', color: 'var(--rouge)', border: 'none', cursor: 'pointer' }}>×</button>
                </div>
              );
            })}
          </div>

          {/* Totaux + notes */}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 220px', gap: 14, marginBottom: 12 }}>
            <textarea value={notes} onChange={e => setNotes(e.target.value)} rows={3} placeholder="Notes pour le client (CGV, conditions…)" style={{ ...inp, resize: 'vertical' }}/>
            <div style={{ padding: 10, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 5 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, marginBottom: 3 }}><span style={{ color: 'var(--ink-4)' }}>HT</span><span className="mono">{fmtEur2(totals.ht)}</span></div>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, marginBottom: 3 }}><span style={{ color: 'var(--ink-4)' }}>TVA</span><span className="mono">{fmtEur2(totals.tva)}</span></div>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 14, fontWeight: 700, paddingTop: 6, borderTop: '1px solid var(--hairline)', color: 'var(--signal-deep)' }}><span>TTC</span><span className="mono">{fmtEur2(totals.ttc)}</span></div>
            </div>
          </div>

          {error && <div style={{ marginBottom: 10, padding: '6px 10px', fontSize: 11, background: 'var(--rouge-tint)', border: '1px solid var(--rouge)', borderRadius: 4, color: 'var(--rouge)' }}>⚠ {error}</div>}

          <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, paddingTop: 10, borderTop: '1px solid var(--hairline)' }}>
            <button onClick={onClose} disabled={busy} style={{ padding: '6px 14px', fontSize: 12, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4 }}>Annuler</button>
            <button onClick={submit} disabled={busy || lines.length === 0} style={{ padding: '6px 14px', fontSize: 12, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 4, fontWeight: 600 }}>{busy ? '…' : '✓ Créer le devis'}</button>
          </div>
        </div>
      </div>
    );
  }

  // ──────────────────────────────────────────────────────────────────────────
  // DevisDetail — détail d'un devis (header, lignes, totaux, actions state)
  // ──────────────────────────────────────────────────────────────────────────
  function DevisDetail({ quoteId, onChanged, onDeleted }) {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [acting, setActing] = useState(null); // 'confirm'|'cancel'|'draft'|'delete'|'save'|null
    const [msg, setMsg] = useState(null);
    const [editMode, setEditMode] = useState(false);
    const [draft, setDraft] = useState({ note: '', validity_date: '' });
    const [editLines, setEditLines] = useState(null); // null = pas en édition, [] = lignes en cours d'édition

    const fetchDetail = () => {
      setLoading(true);
      fetch(`${API()}/api/odoo/quotes/${quoteId}`)
        .then(r => r.json())
        .then(d => {
          setData(d);
          setDraft({
            note: d?.quote?.note || '',
            validity_date: d?.quote?.validity_date || '',
          });
        })
        .catch(() => {})
        .finally(() => setLoading(false));
    };
    useEffect(fetchDetail, [quoteId]);

    const doAction = async (action) => {
      if (!window.confirm(`Action ${action} sur devis ${data?.quote?.name} ?`)) return;
      setActing(action); setMsg(null);
      try {
        const r = await fetch(`${API()}/api/odoo/quotes/${quoteId}`, {
          method: 'PATCH', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ action }),
        }).then(r => r.json());
        if (r?.status === 'ok') {
          setMsg(`✓ Action ${action} réussie`);
          fetchDetail();
          if (onChanged) onChanged();
        } else throw new Error(r?.message || 'Erreur');
      } catch (e) { setMsg(`⚠ ${e.message}`); }
      setActing(null);
      setTimeout(() => setMsg(null), 3000);
    };

    const saveEdits = async () => {
      setActing('save'); setMsg(null);
      try {
        const body = {};
        if (draft.note !== (data?.quote?.note || '')) body.note = draft.note;
        if (draft.validity_date !== (data?.quote?.validity_date || '')) body.validity_date = draft.validity_date;
        if (Object.keys(body).length === 0) { setEditMode(false); setActing(null); return; }
        const r = await fetch(`${API()}/api/odoo/quotes/${quoteId}`, {
          method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
        }).then(r => r.json());
        if (r?.status === 'ok') {
          setMsg('✓ Enregistré');
          setEditMode(false);
          fetchDetail();
          if (onChanged) onChanged();
        } else throw new Error(r?.message || 'Erreur');
      } catch (e) { setMsg(`⚠ ${e.message}`); }
      setActing(null);
      setTimeout(() => setMsg(null), 3000);
    };

    const saveLines = async () => {
      setActing('save-lines'); setMsg(null);
      try {
        const r = await fetch(`${API()}/api/odoo/quotes/${quoteId}`, {
          method: 'PATCH', headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ lines: editLines }),
        }).then(r => r.json());
        if (r?.status === 'ok') {
          setMsg('✓ Lignes enregistrées');
          setEditLines(null);
          fetchDetail();
          if (onChanged) onChanged();
        } else throw new Error(r?.message || 'Erreur');
      } catch (e) { setMsg(`⚠ ${e.message}`); }
      setActing(null);
      setTimeout(() => setMsg(null), 3000);
    };

    const doDelete = async () => {
      if (!window.confirm(`Supprimer définitivement le devis ${data?.quote?.name} ?\n\nCette action est irréversible dans Odoo.`)) return;
      setActing('delete'); setMsg(null);
      try {
        const r = await fetch(`${API()}/api/odoo/quotes/${quoteId}`, { method: 'DELETE' }).then(r => r.json());
        if (r?.status === 'ok') {
          if (onDeleted) onDeleted();
          if (onChanged) onChanged();
        } else throw new Error(r?.message || 'Erreur suppression');
      } catch (e) {
        setActing(null);
        setMsg(`⚠ ${e.message}`);
        setTimeout(() => setMsg(null), 5000);
      }
    };

    if (loading) return <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Chargement devis…</div>;
    if (!data || !data.quote) return <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Devis introuvable.</div>;

    const { quote, lines = [], attachments = [], messages = [], activities = [] } = data;
    const meta = STATE_META[quote.state] || STATE_META.draft;
    const partnerName = Array.isArray(quote.partner_id) ? quote.partner_id[1] : '—';
    const canConfirm = ['draft', 'sent'].includes(quote.state);
    const canCancel = ['draft', 'sent', 'sale'].includes(quote.state);
    const canDraft = ['cancel'].includes(quote.state);

    return (
      <>
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, borderBottom: '1px solid var(--hairline)', paddingBottom: 12, marginBottom: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 18, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 8 }}>
              🧾 {quote.name}
              <Badge tone={meta.tone}>{meta.label}</Badge>
            </div>
            <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>{partnerName} · {fmtDate(quote.date_order)} · Odoo #{quote.id}</div>
          </div>
          <div style={{ display: 'flex', gap: 5 }}>
            {!editMode && !editLines && quote.state === 'draft' && (
              <>
                <button onClick={() => setEditMode(true)} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper)', color: 'var(--ink)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>✏️ Éditer</button>
                <button onClick={() => setEditLines(lines.map(l => ({
                  id: l.id,
                  product_id: Array.isArray(l.product_id) ? l.product_id[0] : l.product_id || null,
                  product_name: Array.isArray(l.product_id) ? l.product_id[1] : '',
                  name: l.name || '',
                  qty: l.product_uom_qty || 1,
                  price_unit: l.price_unit || 0,
                })))} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper)', color: 'var(--ink)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>📋 Lignes</button>
              </>
            )}
            {editMode && (
              <>
                <button onClick={() => setEditMode(false)} disabled={acting === 'save'} style={{ padding: '5px 10px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4 }}>Annuler</button>
                <button onClick={saveEdits} disabled={acting === 'save'} style={{ padding: '5px 12px', fontSize: 11, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 4, fontWeight: 600 }}>{acting === 'save' ? '…' : '✓ Enregistrer'}</button>
              </>
            )}
            {!editMode && !editLines && (
              <button onClick={doDelete} disabled={!!acting} title="Supprimer ce devis" style={{ padding: '5px 10px', fontSize: 11, background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 4, cursor: 'pointer' }}>{acting === 'delete' ? '…' : '🗑'}</button>
            )}
          </div>
        </div>

        {msg && <div style={{ marginBottom: 10, padding: '6px 10px', fontSize: 11, background: msg.startsWith('⚠') ? 'var(--rouge-tint)' : 'var(--signal-tint)', border: '1px solid ' + (msg.startsWith('⚠') ? 'var(--rouge)' : 'var(--signal-soft)'), borderRadius: 5, color: msg.startsWith('⚠') ? 'var(--rouge)' : 'var(--signal-deep)' }}>{msg}</div>}

        {/* Stats + actions */}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr auto', gap: 10, marginBottom: 18, alignItems: 'center' }}>
          <div>
            <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>Total HT</div>
            <div className="mono" style={{ fontSize: 16, fontWeight: 600 }}>{fmtEur2(quote.amount_untaxed)}</div>
          </div>
          <div>
            <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>TVA</div>
            <div className="mono" style={{ fontSize: 16, fontWeight: 600 }}>{fmtEur2(quote.amount_tax)}</div>
          </div>
          <div>
            <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>Total TTC</div>
            <div className="mono" style={{ fontSize: 16, fontWeight: 600, color: 'var(--signal-deep)' }}>{fmtEur2(quote.amount_total)}</div>
          </div>
          <div style={{ display: 'flex', gap: 5, alignItems: 'center' }}>
            {window.ClientAnalyzeButton && <window.ClientAnalyzeButton quoteId={quoteId} />}
            {canConfirm && <button onClick={() => doAction('confirm')} disabled={acting} style={{ padding: '6px 10px', fontSize: 11, fontWeight: 600, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 4, cursor: 'pointer' }}>{acting === 'confirm' ? '…' : '✓ Confirmer'}</button>}
            {canCancel && <button onClick={() => doAction('cancel')} disabled={acting} style={{ padding: '6px 10px', fontSize: 11, fontWeight: 500, background: 'var(--rouge-tint)', color: 'var(--rouge)', border: '1px solid var(--rouge)', borderRadius: 4, cursor: 'pointer' }}>{acting === 'cancel' ? '…' : '⊘ Annuler'}</button>}
            {canDraft && <button onClick={() => doAction('draft')} disabled={acting} style={{ padding: '6px 10px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>{acting === 'draft' ? '…' : '↺ Brouillon'}</button>}
          </div>
        </div>

        {/* Éditable : note + validité */}
        {editMode && (
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 200px', gap: 10, marginBottom: 16, padding: 12, border: '1px solid var(--line)', borderRadius: 5, background: 'var(--paper)' }}>
            <div>
              <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Note</div>
              <textarea value={draft.note} onChange={e => setDraft(d => ({ ...d, note: e.target.value }))} rows={3}
                style={{ width: '100%', padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', fontFamily: 'inherit', resize: 'vertical', boxSizing: 'border-box' }}/>
            </div>
            <div>
              <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Date validité</div>
              <input type="date" value={draft.validity_date || ''} onChange={e => setDraft(d => ({ ...d, validity_date: e.target.value }))}
                style={{ width: '100%', padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', fontFamily: 'inherit', boxSizing: 'border-box' }}/>
            </div>
          </div>
        )}

        {/* Lignes — éditeur inline (mode édition lignes) */}
        {editLines !== null ? (
          <div style={{ marginBottom: 16 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
              <span style={{ fontSize: 12, fontWeight: 600 }}>📋 Modifier les lignes</span>
              <span style={{ flex: 1 }}/>
              <button onClick={() => setEditLines(l => [...l, { id: Date.now(), product_id: null, product_name: '', name: '', qty: 1, price_unit: 0 }])}
                style={{ padding: '4px 10px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>+ Ligne</button>
              <button onClick={() => setEditLines(null)} style={{ padding: '4px 8px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>Annuler</button>
              <button onClick={saveLines} disabled={acting === 'save-lines'}
                style={{ padding: '4px 12px', fontSize: 11, fontWeight: 600, background: 'var(--signal)', color: 'var(--ink)', border: 'none', borderRadius: 4, cursor: 'pointer' }}>
                {acting === 'save-lines' ? '…' : '✓ Enregistrer les lignes'}
              </button>
            </div>
            <div style={{ border: '1px solid var(--line)', borderRadius: 5, overflow: 'hidden' }}>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 80px 110px 110px 36px', padding: '6px 10px', background: 'var(--paper-2)', fontSize: 10, fontWeight: 600, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4, borderBottom: '1px solid var(--hairline)' }}>
                {['Désignation / Produit Odoo', 'Qté', 'Prix U.', 'Total', ''].map(h => <div key={h}>{h}</div>)}
              </div>
              {editLines.length === 0 && <div style={{ padding: 10, fontSize: 11, color: 'var(--ink-4)' }}>Aucune ligne. Clique + pour ajouter.</div>}
              {editLines.map((l, i) => (
                <LineRow key={l.id || i} line={l}
                  onChange={p => setEditLines(ls => ls.map((x, j) => j === i ? { ...x, ...p } : x))}
                  onRemove={() => setEditLines(ls => ls.filter((_, j) => j !== i))}
                  last={i === editLines.length - 1} />
              ))}
              {editLines.length > 0 && (
                <div style={{ display: 'flex', justifyContent: 'flex-end', padding: '8px 12px', background: 'var(--paper-2)', borderTop: '1px solid var(--hairline)' }}>
                  <span className="mono" style={{ fontSize: 14, fontWeight: 700, color: 'var(--signal-deep)' }}>
                    {fmtEur2(editLines.reduce((s, l) => s + ((Number(l.qty) || 0) * (Number(l.price_unit) || 0)), 0))} HT
                  </span>
                </div>
              )}
            </div>
          </div>
        ) : (
          <>
            {/* Lignes — vue lecture */}
            <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 8 }}>
              Lignes ({lines.length})
            </div>
            <div style={{ border: '1px solid var(--line)', borderRadius: 5, overflow: 'hidden', marginBottom: 16 }}>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 90px 110px 110px', padding: '8px 12px', background: 'var(--paper)', fontSize: 10, fontWeight: 600, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.4, borderBottom: '1px solid var(--hairline)' }}>
                <div>Désignation</div>
                <div style={{ textAlign: 'right' }}>Qté</div>
                <div style={{ textAlign: 'right' }}>PU</div>
                <div style={{ textAlign: 'right' }}>Sous-total</div>
              </div>
              {lines.length === 0 && <div style={{ padding: 10, fontSize: 11, color: 'var(--ink-4)' }}>Aucune ligne.</div>}
              {lines.map(l => (
                <div key={l.id} style={{ display: 'grid', gridTemplateColumns: '1fr 90px 110px 110px', padding: '8px 12px', fontSize: 12, borderBottom: '1px solid var(--hairline)', alignItems: 'center', background: 'var(--paper-2)' }}>
                  <div>
                    <div style={{ fontWeight: 500 }}>{Array.isArray(l.product_id) ? l.product_id[1] : (l.name || '—')}</div>
                    {l.name && Array.isArray(l.product_id) && l.name !== l.product_id[1] && <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>{l.name}</div>}
                  </div>
                  <div className="mono" style={{ textAlign: 'right' }}>{l.product_uom_qty}</div>
                  <div className="mono" style={{ textAlign: 'right' }}>{fmtEur2(l.price_unit)}</div>
                  <div className="mono" style={{ textAlign: 'right', fontWeight: 600 }}>{fmtEur2(l.price_subtotal)}</div>
                </div>
              ))}
            </div>
          </>
        )}

        {/* Note */}
        {!editMode && quote.note && (
          <div style={{ padding: 12, background: 'var(--paper)', border: '1px solid var(--line)', borderRadius: 5, fontSize: 12, marginBottom: 16, whiteSpace: 'pre-wrap' }}>
            <div style={{ fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 4 }}>Note</div>
            {quote.note}
          </div>
        )}

        {/* Attachments */}
        {attachments.length > 0 && (() => {
          const API = (window.AE_API && window.AE_API.BASE) || '';
          const images = attachments.filter(a => a.mimetype?.startsWith('image/'));
          const others = attachments.filter(a => !a.mimetype?.startsWith('image/'));
          return (
            <>
              <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 8 }}>
                Pièces jointes ({attachments.length})
              </div>
              {images.length > 0 && (
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(110px, 1fr))', gap: 6, marginBottom: 10 }}>
                  {images.map(a => (
                    <a key={a.id} href={API + a.url} target="_blank" rel="noopener" title={a.name} style={{
                      position: 'relative', display: 'block', aspectRatio: '1', overflow: 'hidden',
                      border: '1px solid var(--hairline)', borderRadius: 4, background: 'var(--paper)',
                      textDecoration: 'none',
                    }}>
                      <img src={API + (a.preview_url || a.url)} alt={a.name} loading="lazy" style={{
                        width: '100%', height: '100%', objectFit: 'cover', display: 'block',
                      }}/>
                      <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: '3px 5px',
                        background: 'rgba(0,0,0,.55)', color: '#fff', fontSize: 9, lineHeight: 1.2,
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.name}</div>
                    </a>
                  ))}
                </div>
              )}
              {others.length > 0 && (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 5, marginBottom: 16 }}>
                  {others.map(a => (
                    <a key={a.id} href={API + a.url} target="_blank" rel="noopener" style={{
                      display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px',
                      background: 'var(--paper)', border: '1px solid var(--hairline)', borderRadius: 4,
                      fontSize: 12, color: 'var(--ink-2)', textDecoration: 'none',
                    }}>
                      <span style={{ fontSize: 14 }}>{a.mimetype?.includes('pdf') ? '📄' : '📎'}</span>
                      <span style={{ flex: 1 }}>{a.name}</span>
                      <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>{Math.round((a.file_size || 0) / 1024)} Ko</span>
                    </a>
                  ))}
                </div>
              )}
              {images.length > 0 && others.length === 0 && <div style={{ marginBottom: 16 }}/>}
            </>
          );
        })()}

        {/* Activities (tâches planifiées) */}
        {activities.length > 0 && (
          <>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 8 }}>
              Tâches planifiées ({activities.length})
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 5, marginBottom: 16 }}>
              {activities.map(a => {
                const typ = Array.isArray(a.activity_type_id) ? a.activity_type_id[1] : '—';
                const user = Array.isArray(a.user_id) ? a.user_id[1] : '—';
                const overdue = a.date_deadline && new Date(a.date_deadline) < new Date();
                return (
                  <div key={a.id} style={{
                    display: 'grid', gridTemplateColumns: 'auto 1fr auto', gap: 10, padding: '6px 10px',
                    background: 'var(--paper)', border: '1px solid ' + (overdue ? 'var(--rouge)' : 'var(--hairline)'),
                    borderRadius: 4, fontSize: 11, alignItems: 'center',
                  }}>
                    <span style={{ fontSize: 10, padding: '1px 6px', background: overdue ? 'var(--rouge-tint)' : 'var(--paper-2)', color: overdue ? 'var(--rouge)' : 'var(--ink-3)', borderRadius: 3, fontWeight: 500 }}>{typ}</span>
                    <div>
                      <div style={{ fontWeight: 500 }}>{a.summary || typ}</div>
                      {a.note && <div style={{ fontSize: 10, color: 'var(--ink-4)' }} dangerouslySetInnerHTML={{ __html: (a.note || '').replace(/<[^>]+>/g, '').slice(0, 150) }}/>}
                    </div>
                    <div style={{ textAlign: 'right', fontSize: 10, color: overdue ? 'var(--rouge)' : 'var(--ink-4)' }}>
                      {a.date_deadline || '—'}<br/><span style={{ fontSize: 9 }}>{user}</span>
                    </div>
                  </div>
                );
              })}
            </div>
          </>
        )}

        {/* Messages chatter */}
        {messages.length > 0 && (
          <>
            <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.6, fontWeight: 600, marginBottom: 8 }}>
              Historique ({messages.length})
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
              {messages.slice(0, 5).map((m, i) => {
                const author = Array.isArray(m.author_id) ? m.author_id[1] : 'Système';
                return (
                  <div key={m.id || i} style={{ padding: '6px 10px', background: 'var(--paper)', borderLeft: '2px solid var(--plasma)', fontSize: 11 }}>
                    <div style={{ fontSize: 10, color: 'var(--ink-4)', marginBottom: 2 }}>{author} · {fmtDate(m.date)}</div>
                    <div style={{ color: 'var(--ink-2)' }} dangerouslySetInnerHTML={{ __html: (m.body || '').replace(/<p>/g, '').replace(/<\/p>/g, '\n').slice(0, 300) }}/>
                  </div>
                );
              })}
            </div>
          </>
        )}

        {/* Chatter natif Discovery — photos/notes/relances stockées en local, en plus d'Odoo */}
        {window.ChatterPanel && quote.id && (
          <window.ChatterPanel entityType="odoo_quote" entityId={quote.id} />
        )}
      </>
    );
  }

  // ──────────────────────────────────────────────────────────────────────────
  // NewDevisModal — builder de nouveau devis (partner, lignes, total, save)
  // ──────────────────────────────────────────────────────────────────────────
  function NewDevisModal({ onClose, onCreated, prefillPartnerId }) {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [partnerId, setPartnerId] = useState(prefillPartnerId || null);
    const [partnerName, setPartnerName] = useState('');
    const [partnerQuery, setPartnerQuery] = useState('');
    const [partnerResults, setPartnerResults] = useState([]);
    const [partnerSearching, setPartnerSearching] = useState(false);
    const [validityDate, setValidityDate] = useState('');
    const [note, setNote] = useState('');
    const [lines, setLines] = useState([]);
    const [busy, setBusy] = useState(false);
    const [error, setError] = useState(null);
    const [activeTpl, setActiveTpl] = useState(null);

    // Search partner (debounced)
    useEffect(() => {
      if (!partnerQuery || partnerQuery.length < 2 || partnerId) { setPartnerResults([]); return; }
      let cancelled = false;
      const t = setTimeout(() => {
        setPartnerSearching(true);
        fetch(`${API()}/api/odoo/partners?search=${encodeURIComponent(partnerQuery)}&limit=20`)
          .then(r => r.json())
          .then(d => { if (!cancelled) setPartnerResults(d?.partners || []); })
          .catch(() => setPartnerResults([]))
          .finally(() => { if (!cancelled) setPartnerSearching(false); });
      }, 250);
      return () => { cancelled = true; clearTimeout(t); };
    }, [partnerQuery, partnerId]);

    const applyTemplate = (key) => {
      const tpl = DV_TEMPLATES[key];
      if (!tpl) return;
      setLines(tpl.lines.map(tplToOdooLine));
      setActiveTpl(key);
    };

    const addLine = () => {
      setLines(ls => [...ls, { id: Date.now(), product_id: null, product_name: '', name: '', qty: 1, price_unit: 0 }]);
    };

    const updateLine = (id, patch) => {
      setLines(ls => ls.map(l => l.id === id ? { ...l, ...patch } : l));
    };

    const removeLine = (id) => {
      setLines(ls => ls.filter(l => l.id !== id));
    };

    const totalHT = useMemo(() => lines.reduce((s, l) => s + ((Number(l.qty) || 0) * (Number(l.price_unit) || 0)), 0), [lines]);

    const save = async () => {
      if (!partnerId) { setError('Client requis'); return; }
      setBusy(true); setError(null);
      try {
        const payload = {
          partner_id: partnerId,
          validity_date: validityDate || null,
          note: note || null,
          lines: lines.map(l => ({
            product_id: l.product_id || null,
            name: l.name || l.product_name || '',
            qty: Number(l.qty) || 1,
            price_unit: Number(l.price_unit) || 0,
          })),
        };
        const r = await fetch(`${API()}/api/odoo/quotes`, {
          method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload),
        }).then(r => r.json());
        if (r?.status !== 'ok') throw new Error(r?.message || 'Erreur création');
        if (onCreated) onCreated(r.id);
        if (window.AE_API?.hydrate) window.AE_API.hydrate();
      } catch (e) { setError(e.message); }
      setBusy(false);
    };

    return (
      <div style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(14,16,16,0.6)', display: 'grid', placeItems: 'center', backdropFilter: 'blur(3px)', padding: 16 }}
        onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
        <div style={{ width: 'min(840px, 100%)', maxHeight: '92vh', overflowY: 'auto', background: 'var(--paper)', borderRadius: 12, boxShadow: 'var(--shadow-3)', border: '1px solid var(--line-2)' }}>

          <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10, position: 'sticky', top: 0, background: 'var(--paper)', zIndex: 2 }}>
            <Dot tone="signal" size={8} pulse/>
            <div>
              <div style={{ fontSize: 14, fontWeight: 600 }}>Nouveau devis</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Odoo sale.order · création manuelle</div>
            </div>
            <span style={{ flex: 1 }}/>
            <button onClick={onClose} style={{ color: 'var(--ink-4)' }}><Icon.cross/></button>
          </div>

          <div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>

            {/* Client — partner picker */}
            <div>
              <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 6 }}>Client Odoo *</div>
              {partnerId ? (
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', background: 'var(--signal-tint)', border: '1px solid var(--signal-soft)', borderRadius: 5 }}>
                  <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--signal-deep)' }}>✓ {partnerName}</span>
                  <span className="mono" style={{ fontSize: 10, color: 'var(--ink-4)' }}>Odoo #{partnerId}</span>
                  <span style={{ flex: 1 }}/>
                  <button onClick={() => { setPartnerId(null); setPartnerName(''); setPartnerQuery(''); }}
                    style={{ padding: '3px 8px', fontSize: 11, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 3 }}>Changer</button>
                </div>
              ) : (
                <div style={{ position: 'relative' }}>
                  <input value={partnerQuery} onChange={e => setPartnerQuery(e.target.value)} placeholder="Taper au moins 2 lettres… (recherche dans Odoo)" autoFocus
                    style={{ width: '100%', padding: '8px 12px', fontSize: 13, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', boxSizing: 'border-box' }}/>
                  {partnerSearching && <div style={{ position: 'absolute', right: 10, top: 9, fontSize: 11, color: 'var(--ink-4)' }}>…</div>}
                  {partnerResults.length > 0 && (
                    <div style={{ position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 3, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 5, boxShadow: 'var(--shadow-2)', zIndex: 5, maxHeight: 240, overflowY: 'auto' }}>
                      {partnerResults.map(p => (
                        <button key={p.id} type="button" onClick={() => { setPartnerId(p.id); setPartnerName(p.name); setPartnerResults([]); setPartnerQuery(''); }}
                          style={{ display: 'block', width: '100%', textAlign: 'left', padding: '8px 12px', fontSize: 12, borderBottom: '1px solid var(--hairline)', background: 'var(--paper)' }}>
                          <div style={{ fontWeight: 500, display: 'flex', alignItems: 'center', gap: 6 }}>
                            {p.is_company ? '🏢' : '👤'} {p.name}
                          </div>
                          <div style={{ fontSize: 10, color: 'var(--ink-4)' }}>{p.email || p.phone || '—'} · {p.city || '—'}</div>
                        </button>
                      ))}
                    </div>
                  )}
                </div>
              )}
            </div>

            {/* Validity + Note */}
            <div style={{ display: 'grid', gridTemplateColumns: '200px 1fr', gap: 10 }}>
              <div>
                <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 6 }}>Date validité</div>
                <input type="date" value={validityDate} onChange={e => setValidityDate(e.target.value)}
                  style={{ width: '100%', padding: '8px 12px', fontSize: 13, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', boxSizing: 'border-box' }}/>
              </div>
              <div>
                <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, marginBottom: 6 }}>Note</div>
                <input value={note} onChange={e => setNote(e.target.value)} placeholder="Visible sur le devis…"
                  style={{ width: '100%', padding: '8px 12px', fontSize: 13, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', boxSizing: 'border-box' }}/>
              </div>
            </div>

            {/* Lignes + Templates */}
            <div>
              <TemplateBar onSelect={applyTemplate} active={activeTpl} />
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
                <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>Lignes ({lines.length})</div>
                <span style={{ flex: 1 }}/>
                <button onClick={addLine} style={{ padding: '4px 10px', fontSize: 11, fontWeight: 600, background: 'var(--paper-2)', border: '1px solid var(--line-2)', borderRadius: 4, cursor: 'pointer' }}>+ Ajouter une ligne</button>
              </div>
              <div style={{ border: '1px solid var(--line)', borderRadius: 5, overflow: 'hidden' }}>
                {lines.length === 0 && <div style={{ padding: 16, fontSize: 12, color: 'var(--ink-4)', textAlign: 'center' }}>Choisis un modèle ci-dessus ou clique « Ajouter une ligne »</div>}
                {lines.map((l, i) => (
                  <LineRow key={l.id} line={l} onChange={(patch) => updateLine(l.id, patch)} onRemove={() => removeLine(l.id)} last={i === lines.length - 1}/>
                ))}
              </div>
              {lines.length > 0 && (
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', background: 'var(--paper-2)', border: '1px solid var(--line)', borderTop: 'none', borderRadius: '0 0 5px 5px' }}>
                  <span style={{ flex: 1, fontSize: 11, color: 'var(--ink-3)' }}>Total HT estimé (TVA Odoo auto au save)</span>
                  <span className="mono" style={{ fontSize: 16, fontWeight: 700, color: 'var(--signal-deep)' }}>{fmtEur2(totalHT)}</span>
                </div>
              )}
            </div>

            {error && <div style={{ fontSize: 11, color: 'var(--rouge)', padding: '6px 10px', background: 'var(--rouge-tint)', borderRadius: 5 }}>⚠ {error}</div>}

            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
              <button onClick={onClose} style={{ padding: '8px 14px', fontSize: 12, color: 'var(--ink-3)' }}>Annuler</button>
              <button onClick={save} disabled={busy || !partnerId} style={{
                padding: '8px 14px', fontSize: 12, fontWeight: 600,
                background: !partnerId ? 'var(--paper-3)' : 'var(--signal)',
                color: !partnerId ? 'var(--ink-4)' : 'var(--ink)',
                border: 'none', borderRadius: 5,
                cursor: !partnerId ? 'not-allowed' : 'pointer',
              }}>{busy ? 'Création…' : 'Créer le devis'}</button>
            </div>
          </div>
        </div>
      </div>
    );
  }

  // Ligne de devis avec autocomplete produit
  function LineRow({ line, onChange, onRemove, last }) {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [query, setQuery] = useState(line.product_name || '');
    const [results, setResults] = useState([]);
    const [searching, setSearching] = useState(false);
    const [focused, setFocused] = useState(false);

    useEffect(() => { setQuery(line.product_name || ''); }, [line.product_id]);

    useEffect(() => {
      if (!focused || !query || query.length < 2 || line.product_id) { setResults([]); return; }
      let cancelled = false;
      const t = setTimeout(() => {
        setSearching(true);
        fetch(`${API()}/api/odoo/products?q=${encodeURIComponent(query)}&limit=20`)
          .then(r => r.json())
          .then(d => { if (!cancelled) setResults(d?.products || []); })
          .catch(() => setResults([]))
          .finally(() => { if (!cancelled) setSearching(false); });
      }, 250);
      return () => { cancelled = true; clearTimeout(t); };
    }, [query, focused, line.product_id]);

    const selectProduct = (p) => {
      onChange({
        product_id: p.id,
        product_name: p.name,
        name: p.description_sale || p.name,
        price_unit: p.list_price || 0,
      });
      setResults([]);
      setFocused(false);
    };

    const subtotal = (Number(line.qty) || 0) * (Number(line.price_unit) || 0);

    return (
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 80px 110px 110px 36px', gap: 6, padding: '8px 10px', borderBottom: last ? 'none' : '1px solid var(--hairline)', alignItems: 'start', background: 'var(--paper)' }}>
        <div style={{ position: 'relative' }}>
          {line.product_id && (
            <div style={{ fontSize: 10, color: 'var(--signal-deep)', marginBottom: 2, display: 'flex', alignItems: 'center', gap: 4 }}>
              ✓ Produit Odoo #{line.product_id}
              <button onClick={() => onChange({ product_id: null, product_name: '' })} style={{ fontSize: 9, color: 'var(--ink-4)', textDecoration: 'underline', cursor: 'pointer' }}>détacher</button>
            </div>
          )}
          <input
            value={line.product_id ? line.name : query}
            onChange={e => {
              if (line.product_id) onChange({ name: e.target.value });
              else { setQuery(e.target.value); onChange({ product_name: e.target.value, name: e.target.value }); }
            }}
            onFocus={() => setFocused(true)}
            onBlur={() => setTimeout(() => setFocused(false), 200)}
            placeholder={line.product_id ? 'Description (modifiable)' : 'Produit Odoo ou désignation libre…'}
            style={{ width: '100%', padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 3, background: 'var(--paper-2)', fontFamily: 'inherit', boxSizing: 'border-box' }}/>
          {focused && results.length > 0 && !line.product_id && (
            <div style={{ position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 2, background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 4, boxShadow: 'var(--shadow-2)', zIndex: 10, maxHeight: 200, overflowY: 'auto' }}>
              {searching && <div style={{ padding: 6, fontSize: 10, color: 'var(--ink-4)' }}>Recherche…</div>}
              {results.map(p => (
                <button key={p.id} type="button" onMouseDown={(e) => { e.preventDefault(); selectProduct(p); }}
                  style={{ display: 'block', width: '100%', textAlign: 'left', padding: '6px 10px', fontSize: 11, borderBottom: '1px solid var(--hairline)', background: 'var(--paper)' }}>
                  <div style={{ fontWeight: 500 }}>{p.name}</div>
                  <div className="mono" style={{ fontSize: 9, color: 'var(--ink-4)' }}>{p.default_code || '—'} · {fmtEur2(p.list_price)}</div>
                </button>
              ))}
            </div>
          )}
        </div>
        <input type="number" value={line.qty} onChange={e => onChange({ qty: e.target.value })} min="0" step="0.01" placeholder="Qté"
          style={{ width: '100%', padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 3, background: 'var(--paper-2)', textAlign: 'right', fontFamily: 'inherit', boxSizing: 'border-box' }}/>
        <input type="number" value={line.price_unit} onChange={e => onChange({ price_unit: e.target.value })} min="0" step="0.01" placeholder="Prix unitaire"
          style={{ width: '100%', padding: '6px 8px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 3, background: 'var(--paper-2)', textAlign: 'right', fontFamily: 'inherit', boxSizing: 'border-box' }}/>
        <div className="mono" style={{ textAlign: 'right', fontSize: 12, fontWeight: 600, color: 'var(--signal-deep)', padding: '6px 0' }}>{fmtEur2(subtotal)}</div>
        <button onClick={onRemove} style={{ padding: 4, fontSize: 14, color: 'var(--rouge)', background: 'transparent', border: 'none', cursor: 'pointer' }} title="Supprimer la ligne">×</button>
      </div>
    );
  }

  function Stat({ label, value, tone, mono }) {
    return (
      <div style={{ padding: '10px 12px', background: 'var(--paper)' }}>
        <div style={{ fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 5 }}>
          {tone && <Dot tone={tone} size={6}/>}
          {label}
        </div>
        <div className={mono ? 'mono' : ''} style={{ fontSize: mono ? 16 : 15, fontWeight: 600, color: 'var(--ink)', marginTop: 3 }}>{value}</div>
      </div>
    );
  }

  // ──────────────────────────────────────────────────────────────────────────
  // H1 — DevisAEContent : devis natifs Discovery (stockés dans Invoice
  // avec type='proforma'). Builder complet avec lignes éditables, totaux
  // live, conversion en facture, génération PDF.
  // ──────────────────────────────────────────────────────────────────────────
  function DevisAEContent() {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const [devis, setDevis] = useState([]);
    const [loading, setLoading] = useState(true);
    const [builderOpen, setBuilderOpen] = useState(false);
    const [editing, setEditing] = useState(null);

    const reload = async () => {
      setLoading(true);
      try {
        const r = await fetch(`${API()}/api/billing/invoices?type=proforma&limit=200`, { credentials: 'include' }).then(r => r.json());
        setDevis(r.invoices || []);
      } catch (_) { setDevis([]); }
      finally { setLoading(false); }
    };

    useEffect(() => { reload(); }, []);

    const openBuilder = (d) => { setEditing(d || null); setBuilderOpen(true); };
    const close = () => { setBuilderOpen(false); setEditing(null); };

    const convertToInvoice = async (d) => {
      if (!window.confirm(`Convertir le devis ${d.ref} en facture ?\n\nUne nouvelle facture sera créée à partir de ce devis.`)) return;
      try {
        const body = { ...d, type: 'invoice', status: 'draft', ref: undefined, id: undefined };
        const r = await fetch(`${API()}/api/billing/invoices`, {
          method: 'POST', credentials: 'include',
          headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
        }).then(r => r.json());
        if (r.ok) {
          window.toast?.success(`✓ Facture ${r.invoice.ref} créée depuis ${d.ref}`);
          reload();
        } else {
          window.toast?.error(r.error || 'Erreur conversion');
        }
      } catch (e) { window.toast?.error(e.message); }
    };

    const remove = async (d) => {
      if (!window.confirm(`Supprimer le devis ${d.ref} ?`)) return;
      const r = await fetch(`${API()}/api/billing/invoices/${d.id}`, { method: 'DELETE', credentials: 'include' }).then(r => r.json()).catch(() => null);
      if (r?.ok) { window.toast?.success('Supprimé'); reload(); }
      else window.toast?.error(r?.error || 'Erreur');
    };

    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '14px 0', flexWrap: 'wrap' }}>
          <span style={{ fontSize: 13, fontWeight: 600 }}>📄 Devis natifs Audits Énergies</span>
          <span style={{ fontSize: 11, color: 'var(--ink-4)' }}>· {devis.length} devis</span>
          <span style={{ flex: 1 }} />
          <Btn variant="outline" size="sm" onClick={reload}>⟳ Actualiser</Btn>
          <Btn variant="signal" size="sm" icon={<Icon.plus />} onClick={() => openBuilder()}>+ Nouveau devis</Btn>
        </div>

        {loading && (window.Skeleton ? <window.Skeleton kind="rows" count={6} height={64} /> : <div style={{ padding: 20, color: 'var(--ink-4)' }}>Chargement…</div>)}

        {!loading && devis.length === 0 && (
          window.EmptyState ? (
            <window.EmptyState
              icon="📄"
              title="Aucun devis natif"
              sub="Créez votre premier devis depuis le cockpit. Lignes éditables, calcul TVA auto, conversion en facture en 1 clic."
              cta={{ label: '+ Nouveau devis', onClick: () => openBuilder() }}
            />
          ) : null
        )}

        {!loading && devis.length > 0 && (
          <div style={{ border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden', background: 'var(--paper)' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
              <thead>
                <tr style={{ borderBottom: '1px solid var(--line)', background: 'var(--paper-2)' }}>
                  {['Référence', 'Client', 'Date', 'Échéance', 'Statut', 'Total HT', 'Total TTC', 'Actions'].map(h => (
                    <th key={h} style={{ padding: '10px 12px', textAlign: 'left', fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{h}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {devis.map(d => (
                  <tr key={d.id} style={{ borderBottom: '1px solid var(--hairline)' }}>
                    <td style={{ padding: '10px 12px' }}><span className="mono" style={{ fontSize: 10 }}>{d.ref}</span></td>
                    <td style={{ padding: '10px 12px', fontWeight: 500 }}>{d.clientName || '—'}</td>
                    <td style={{ padding: '10px 12px', fontSize: 11, color: 'var(--ink-3)' }}>{d.issuedAt ? new Date(d.issuedAt).toLocaleDateString('fr-FR') : '—'}</td>
                    <td style={{ padding: '10px 12px', fontSize: 11, color: 'var(--ink-3)' }}>{d.dueAt ? new Date(d.dueAt).toLocaleDateString('fr-FR') : '—'}</td>
                    <td style={{ padding: '10px 12px' }}>
                      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '2px 8px', borderRadius: 999, fontSize: 10, background: d.status === 'paid' || d.status === 'sent' ? 'var(--signal-tint)' : 'var(--paper-2)', color: 'var(--ink-2)' }}>{d.status}</span>
                    </td>
                    <td style={{ padding: '10px 12px' }} className="mono">{fmtEur(d.amountHT)}</td>
                    <td style={{ padding: '10px 12px', fontWeight: 600 }} className="mono">{fmtEur(d.amountTTC)}</td>
                    <td style={{ padding: '8px 12px' }}>
                      <div style={{ display: 'flex', gap: 4 }}>
                        <Btn size="sm" variant="ghost" onClick={() => openBuilder(d)} title="Éditer">✎</Btn>
                        <Btn size="sm" variant="ghost" onClick={() => window.open(`${API()}/api/billing/invoices/${d.id}/pdf`, '_blank')} title="Télécharger PDF">📄</Btn>
                        <Btn size="sm" variant="ghost" onClick={async () => {
                          if (!d.clientEmail) { window.toast?.error('Client sans email'); return; }
                          if (!window.confirm(`Envoyer le devis ${d.ref} par email à ${d.clientEmail} ?`)) return;
                          try {
                            const r = await fetch(`${API()}/api/billing/invoices/${d.id}/send-email`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: '{}' }).then(r => r.json());
                            if (r.ok) { window.toast?.success(`✓ Devis envoyé à ${d.clientEmail}`); reload(); }
                            else window.toast?.error(r.error || 'Erreur envoi');
                          } catch (e) { window.toast?.error(e.message); }
                        }} title="Envoyer par email">📤</Btn>
                        <Btn size="sm" variant="ghost" onClick={async () => {
                          if (!d.clientEmail) { window.toast?.error('Client sans email — saisir d\'abord'); return; }
                          if (!window.confirm(`Demander la signature électronique à ${d.clientEmail} ?\n\nUn email sera envoyé avec un lien de signature unique.`)) return;
                          try {
                            const r = await fetch(`${API()}/api/billing/invoices/${d.id}/request-signature`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: '{}' }).then(r => r.json());
                            if (r.ok) {
                              window.toast?.success(`✓ Demande envoyée — ${r.signature.ref}`);
                              if (r.emailWarning) window.toast?.warn?.(`Email non envoyé : ${r.emailWarning}`);
                              reload();
                            } else window.toast?.error(r.error || 'Erreur');
                          } catch (e) { window.toast?.error(e.message); }
                        }} title="Demander signature électronique">✍</Btn>
                        <Btn size="sm" variant="outline" onClick={() => convertToInvoice(d)} title="Convertir en facture">→ F</Btn>
                        <Btn size="sm" variant="ghost" onClick={() => remove(d)} title="Supprimer" style={{ color: 'var(--rouge)' }}>🗑</Btn>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}

        {builderOpen && <DevisBuilderModal devis={editing} onClose={close} onSaved={() => { close(); reload(); }} />}
      </div>
    );
  }

  // Builder modal — création / édition d'un devis natif
  function DevisBuilderModal({ devis, onClose, onSaved }) {
    const API = () => (window.AE_API && window.AE_API.BASE) || '';
    const editMode = !!devis?.id;
    const [form, setForm] = useState(() => ({
      type: 'proforma',
      status: 'draft',
      clientName: devis?.clientName || '',
      clientAddress: devis?.clientAddress || '',
      clientSiret: devis?.clientSiret || '',
      clientEmail: devis?.clientEmail || '',
      contactId: devis?.contactId || null,
      vatRate: devis?.vatRate ?? 20,
      lines: devis?.lines || [{ designation: '', quantity: 1, unitPrice: 0 }],
      issuedAt: devis?.issuedAt ? devis.issuedAt.slice(0, 10) : new Date().toISOString().slice(0, 10),
      dueAt: devis?.dueAt ? devis.dueAt.slice(0, 10) : new Date(Date.now() + 30 * 86400000).toISOString().slice(0, 10),
      notes: devis?.notes || '',
    }));
    const [saving, setSaving] = useState(false);
    const [activeTpl, setActiveTpl] = useState(null);

    // Recalc live
    const totals = useMemo(() => {
      const ht = form.lines.reduce((s, l) => s + (parseFloat(l.quantity || 0) * parseFloat(l.unitPrice || 0)), 0);
      const tva = +(ht * form.vatRate / 100).toFixed(2);
      return { ht, tva, ttc: +(ht + tva).toFixed(2) };
    }, [form.lines, form.vatRate]);

    const applyTemplate = (key) => {
      const tpl = DV_TEMPLATES[key];
      if (!tpl) return;
      setForm(f => ({ ...f, lines: tpl.lines.map(tplToAELine) }));
      setActiveTpl(key);
    };

    const updateLine = (idx, patch) => {
      setForm(f => ({ ...f, lines: f.lines.map((l, i) => i === idx ? { ...l, ...patch } : l) }));
    };
    const addLine = () => setForm(f => ({ ...f, lines: [...f.lines, { designation: '', quantity: 1, unitPrice: 0 }] }));
    const removeLine = (idx) => setForm(f => ({ ...f, lines: f.lines.filter((_, i) => i !== idx) }));

    const save = async () => {
      if (!form.clientName) { window.toast?.error('Nom client requis'); return; }
      setSaving(true);
      try {
        const url = editMode ? `${API()}/api/billing/invoices/${devis.id}` : `${API()}/api/billing/invoices`;
        const method = editMode ? 'PATCH' : 'POST';
        const r = await fetch(url, {
          method, credentials: 'include',
          headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form),
        }).then(r => r.json());
        if (r.ok) {
          window.toast?.success(editMode ? `✓ Devis ${r.invoice.ref} mis à jour` : `✓ Devis ${r.invoice.ref} créé`);
          onSaved();
        } else { window.toast?.error(r.error || 'Erreur'); }
      } catch (e) { window.toast?.error(e.message); }
      finally { setSaving(false); }
    };

    const inp = { padding: '6px 10px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 5, background: 'var(--paper-2)', fontFamily: 'inherit', width: '100%', boxSizing: 'border-box' };
    const lbl = { fontSize: 9, color: 'var(--ink-4)', textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600, display: 'block', marginBottom: 3 };

    return (
      <div onClick={e => { if (e.target === e.currentTarget) onClose(); }}
        style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(14,16,16,0.6)', display: 'grid', placeItems: 'center', backdropFilter: 'blur(3px)', padding: 16 }}>
        <div style={{ width: 'min(820px, 100%)', maxHeight: '92vh', overflowY: 'auto', background: 'var(--paper)', borderRadius: 12, border: '1px solid var(--line-2)', boxShadow: 'var(--shadow-3)' }}>
          <div style={{ padding: '14px 22px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10, position: 'sticky', top: 0, background: 'var(--paper)' }}>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 14, fontWeight: 700 }}>{editMode ? `✎ Édition devis ${devis.ref}` : '+ Nouveau devis natif'}</div>
              <div style={{ fontSize: 11, color: 'var(--ink-4)', marginTop: 2 }}>Builder live · totaux recalculés à chaque saisie</div>
            </div>
            <button onClick={onClose} style={{ fontSize: 16, color: 'var(--ink-4)', padding: 4 }}>✕</button>
          </div>

          <div style={{ padding: 22 }}>
            {/* Client */}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 16 }}>
              <div style={{ gridColumn: '1 / -1', display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ fontSize: 13, fontWeight: 600 }}>👤 Client</span>
                {window.ClientPicker && (
                  <window.ClientPicker
                    value={null}
                    onChange={c => {
                      if (c) setForm(f => ({ ...f, clientName: c.name || '', clientEmail: c.email || '', clientSiret: c.siret || '', clientAddress: [c.street, c.zip, c.city].filter(Boolean).join(' ') }));
                    }}
                    placeholder="Lier à un client existant…"
                  />
                )}
              </div>
              <label><span style={lbl}>Raison sociale</span><input value={form.clientName} onChange={e => setForm(f => ({ ...f, clientName: e.target.value }))} style={inp} /></label>
              <label><span style={lbl}>SIRET</span><input value={form.clientSiret} onChange={e => setForm(f => ({ ...f, clientSiret: e.target.value }))} style={inp} /></label>
              <label style={{ gridColumn: '1 / -1' }}><span style={lbl}>Adresse</span><input value={form.clientAddress} onChange={e => setForm(f => ({ ...f, clientAddress: e.target.value }))} style={inp} /></label>
              <label><span style={lbl}>Email</span><input type="email" value={form.clientEmail} onChange={e => setForm(f => ({ ...f, clientEmail: e.target.value }))} style={inp} /></label>
              <label><span style={lbl}>Date émission</span><input type="date" value={form.issuedAt} onChange={e => setForm(f => ({ ...f, issuedAt: e.target.value }))} style={inp} /></label>
              <label><span style={lbl}>Échéance</span><input type="date" value={form.dueAt} onChange={e => setForm(f => ({ ...f, dueAt: e.target.value }))} style={inp} /></label>
              <label><span style={lbl}>TVA (%)</span><input type="number" value={form.vatRate} onChange={e => setForm(f => ({ ...f, vatRate: parseFloat(e.target.value) || 0 }))} style={inp} /></label>
            </div>

            {/* Lignes + Templates */}
            <div style={{ marginBottom: 16 }}>
              {!editMode && <TemplateBar onSelect={applyTemplate} active={activeTpl} />}
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
                <span style={{ fontSize: 13, fontWeight: 600 }}>📋 Lignes</span>
                <span style={{ flex: 1 }} />
                <Btn variant="outline" size="sm" icon={<Icon.plus />} onClick={addLine}>Ajouter ligne</Btn>
              </div>
              <div style={{ border: '1px solid var(--line)', borderRadius: 6, overflow: 'hidden' }}>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 80px 110px 110px 36px', gap: 1, background: 'var(--line)', fontSize: 10, color: 'var(--ink-4)', textTransform: 'uppercase', fontWeight: 600 }}>
                  {['Désignation', 'Qté', 'Prix unitaire', 'Total HT', ''].map(h => (
                    <div key={h} style={{ padding: '6px 10px', background: 'var(--paper-2)' }}>{h}</div>
                  ))}
                </div>
                {form.lines.map((l, i) => {
                  const total = (parseFloat(l.quantity || 0) * parseFloat(l.unitPrice || 0));
                  return (
                    <div key={i} style={{ display: 'grid', gridTemplateColumns: '1fr 80px 110px 110px 36px', gap: 1, background: 'var(--line)' }}>
                      <input value={l.designation} onChange={e => updateLine(i, { designation: e.target.value })} placeholder="Description…" style={{ ...inp, borderRadius: 0, border: 'none', background: 'var(--paper)' }} />
                      <input type="number" value={l.quantity} onChange={e => updateLine(i, { quantity: parseFloat(e.target.value) || 0 })} style={{ ...inp, borderRadius: 0, border: 'none', background: 'var(--paper)', textAlign: 'right' }} />
                      <input type="number" step="0.01" value={l.unitPrice} onChange={e => updateLine(i, { unitPrice: parseFloat(e.target.value) || 0 })} style={{ ...inp, borderRadius: 0, border: 'none', background: 'var(--paper)', textAlign: 'right' }} />
                      <div style={{ padding: '6px 10px', background: 'var(--paper-2)', fontFamily: 'var(--font-mono)', fontSize: 12, textAlign: 'right' }}>{fmtEur2(total)}</div>
                      <button onClick={() => removeLine(i)} style={{ background: 'var(--rouge-tint)', color: 'var(--rouge)', border: 'none', cursor: 'pointer' }}>🗑</button>
                    </div>
                  );
                })}
              </div>
            </div>

            {/* Totaux */}
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 30, padding: '14px 18px', background: 'var(--paper-2)', borderRadius: 8, marginBottom: 16 }}>
              <div style={{ textAlign: 'right' }}>
                <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>Total HT</div>
                <div style={{ fontSize: 16, fontWeight: 700 }} className="mono">{fmtEur2(totals.ht)}</div>
              </div>
              <div style={{ textAlign: 'right' }}>
                <div style={{ fontSize: 11, color: 'var(--ink-4)' }}>TVA {form.vatRate}%</div>
                <div style={{ fontSize: 16, fontWeight: 700 }} className="mono">{fmtEur2(totals.tva)}</div>
              </div>
              <div style={{ textAlign: 'right', borderLeft: '1px solid var(--line-2)', paddingLeft: 30 }}>
                <div style={{ fontSize: 11, color: 'var(--signal-deep)', textTransform: 'uppercase', fontWeight: 700 }}>Total TTC</div>
                <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--signal-deep)' }} className="mono">{fmtEur2(totals.ttc)}</div>
              </div>
            </div>

            {/* Notes */}
            <label style={{ display: 'block', marginBottom: 16 }}>
              <span style={lbl}>Notes / conditions</span>
              <textarea value={form.notes} onChange={e => setForm(f => ({ ...f, notes: e.target.value }))} rows={3} style={{ ...inp, resize: 'vertical' }} />
            </label>

            {/* Actions */}
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
              <Btn variant="ghost" onClick={onClose}>Annuler</Btn>
              <Btn variant="signal" onClick={save} disabled={saving}>{saving ? '⏳ Enregistrement…' : (editMode ? '💾 Enregistrer' : '✓ Créer le devis')}</Btn>
            </div>
          </div>
        </div>
      </div>
    );
  }

  // ──────────────────────────────────────────────────────────────────────────
  // DevisSettingsModal — Personnalisation devis (Règle n°6)
  // Persiste dans localStorage les préférences (TVA, validité défaut, mentions légales,
  // template email d'envoi, sync auto Odoo, etc.)
  // ──────────────────────────────────────────────────────────────────────────
  function DevisSettingsModal({ onClose }) {
    const STORAGE_KEY = 'ae_devis_settings';
    const [settings, setSettings] = useState(() => {
      try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || defaults(); }
      catch { return defaults(); }
    });
    const [saved, setSaved] = useState(false);

    function defaults() {
      return {
        tvaDefault: 20,
        validityDays: 30,
        mentionsLegales: 'Devis valable {validityDays} jours. Acompte 30% à la commande, solde à réception du chantier. TVA non applicable, art. 293 B du CGI (si autoliquidation).',
        emailTemplate: 'Bonjour {clientName},\n\nVous trouverez en pièce jointe le devis {ref} pour {description}.\n\nN\'hésitez pas à revenir vers nous pour toute question.\n\nCordialement,\nL\'équipe Audits Énergies',
        syncAutoOdoo: true,
        syncIntervalMin: 15,
        defaultDelegataire: 'akea',
        autoLineFromCatalog: true,
        showVatColumn: true,
        defaultPaymentTerms: '30j fin de mois',
      };
    }

    const update = (k, v) => setSettings(s => ({ ...s, [k]: v }));

    const save = () => {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
      setSaved(true);
      setTimeout(() => setSaved(false), 2000);
    };

    const reset = () => {
      if (!confirm('Réinitialiser tous les paramètres devis ?')) return;
      setSettings(defaults());
    };

    return (
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(0,0,0,.5)', zIndex: 9999,
        display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
      }}>
        <div onClick={e => e.stopPropagation()} style={{
          background: 'var(--paper)', borderRadius: 10, maxWidth: 720, width: '100%',
          maxHeight: '90vh', overflowY: 'auto', boxShadow: '0 8px 40px rgba(0,0,0,.3)',
          fontFamily: 'inherit',
        }}>
          <div style={{ padding: '14px 20px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', gap: 10 }}>
            <div style={{ fontSize: 16, fontWeight: 700, flex: 1 }}>⚙️ Personnalisation devis</div>
            <button onClick={onClose} style={{ padding: 4, fontSize: 16 }}>×</button>
          </div>

          <div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 18 }}>
            {/* Valeurs par défaut */}
            <div>
              <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Valeurs par défaut</div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                <Field label="TVA défaut (%)">
                  <input type="number" value={settings.tvaDefault} onChange={e => update('tvaDefault', parseFloat(e.target.value) || 0)} style={inpStyle()} />
                </Field>
                <Field label="Validité défaut (jours)">
                  <input type="number" value={settings.validityDays} onChange={e => update('validityDays', parseInt(e.target.value) || 30)} style={inpStyle()} />
                </Field>
                <Field label="Délégataire par défaut">
                  <select value={settings.defaultDelegataire} onChange={e => update('defaultDelegataire', e.target.value)} style={inpStyle()}>
                    <option value="akea">AKEA</option>
                    <option value="abokeen">Abokéen</option>
                  </select>
                </Field>
                <Field label="Conditions de paiement">
                  <input value={settings.defaultPaymentTerms} onChange={e => update('defaultPaymentTerms', e.target.value)} style={inpStyle()} />
                </Field>
              </div>
            </div>

            {/* Mentions légales */}
            <div>
              <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Mentions légales (PDF)</div>
              <textarea value={settings.mentionsLegales} onChange={e => update('mentionsLegales', e.target.value)} rows={3}
                style={{ ...inpStyle(), resize: 'vertical', fontFamily: 'inherit' }}/>
              <div style={{ fontSize: 10, color: 'var(--ink-5)', marginTop: 4 }}>Variables : {`{validityDays}`}</div>
            </div>

            {/* Template email */}
            <div>
              <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Template email d'envoi</div>
              <textarea value={settings.emailTemplate} onChange={e => update('emailTemplate', e.target.value)} rows={5}
                style={{ ...inpStyle(), resize: 'vertical', fontFamily: 'inherit' }}/>
              <div style={{ fontSize: 10, color: 'var(--ink-5)', marginTop: 4 }}>Variables : {`{clientName}, {ref}, {description}`}</div>
            </div>

            {/* Sync Odoo */}
            <div>
              <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Synchronisation Odoo</div>
              <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, marginBottom: 8 }}>
                <input type="checkbox" checked={settings.syncAutoOdoo} onChange={e => update('syncAutoOdoo', e.target.checked)} />
                Synchronisation automatique
              </label>
              {settings.syncAutoOdoo && (
                <Field label="Intervalle (minutes)">
                  <input type="number" value={settings.syncIntervalMin} onChange={e => update('syncIntervalMin', parseInt(e.target.value) || 15)} style={inpStyle()} min={5} max={60}/>
                </Field>
              )}
            </div>

            {/* Affichage */}
            <div>
              <div className="mono" style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.05em', fontWeight: 700, marginBottom: 8 }}>Affichage</div>
              <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, marginBottom: 6 }}>
                <input type="checkbox" checked={settings.showVatColumn} onChange={e => update('showVatColumn', e.target.checked)} />
                Afficher la colonne TVA dans la liste
              </label>
              <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12 }}>
                <input type="checkbox" checked={settings.autoLineFromCatalog} onChange={e => update('autoLineFromCatalog', e.target.checked)} />
                Auto-remplir lignes depuis le catalogue produits (EPREL)
              </label>
            </div>

            {/* Actions */}
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', paddingTop: 12, borderTop: '1px solid var(--hairline)' }}>
              <button onClick={reset} style={{ padding: '8px 14px', fontSize: 12, color: 'var(--ink-3)', background: 'var(--paper)', border: '1px solid var(--line-2)', borderRadius: 5, cursor: 'pointer' }}>↺ Réinitialiser</button>
              <button onClick={save} style={{ padding: '8px 18px', fontSize: 12, fontWeight: 600, color: saved ? 'var(--paper)' : 'var(--paper)', background: saved ? 'var(--signal-deep)' : 'var(--ink)', border: 'none', borderRadius: 5, cursor: 'pointer' }}>
                {saved ? '✓ Enregistré' : 'Enregistrer'}
              </button>
            </div>
          </div>
        </div>
      </div>
    );

    function Field({ label, children }) {
      return (
        <div>
          <div style={{ fontSize: 10, color: 'var(--ink-5)', textTransform: 'uppercase', letterSpacing: '.04em', fontWeight: 600, marginBottom: 4 }}>{label}</div>
          {children}
        </div>
      );
    }
    function inpStyle() {
      return { width: '100%', padding: '6px 10px', fontSize: 12, border: '1px solid var(--line-2)', borderRadius: 4, background: 'var(--paper-2)', color: 'var(--ink)', boxSizing: 'border-box' };
    }
  }

  window.DevisPage = DevisPage;
  window.NewDevisModal = NewDevisModal;
  window.DevisSettingsModal = DevisSettingsModal;
})();
