function ReconciliationPage({ projects, rows, values, reconciliations, setReconciliations, loggedUser }) {
  const _storageKey = `tresoimmo_recon_month_${loggedUser || 'default'}`;
  const [selectedMonth, setSelectedMonth] = React.useState(() => {
    try {
      const saved = localStorage.getItem(`tresoimmo_recon_month_${loggedUser || 'default'}`);
      if (saved && /^\d{4}-\d{2}$/.test(saved)) return saved;
    } catch {}
    const d = new Date();
    return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`;
  });
  React.useEffect(() => {
    try { localStorage.setItem(_storageKey, selectedMonth); } catch {}
  }, [selectedMonth, _storageKey]);

  // ── État du mois sélectionné ────────────────────────────────
  const recon = reconciliations[selectedMonth] || { status:'brouillon', statements:[], matches:[] };
  const setRecon = React.useCallback(upd => {
    setReconciliations(prev => {
      const cur = prev[selectedMonth] || { status:'brouillon', statements:[], matches:[] };
      const next = typeof upd === 'function' ? upd(cur) : upd;
      return { ...prev, [selectedMonth]: { ...next, updatedAt: new Date().toISOString() } };
    });
  }, [selectedMonth, setReconciliations]);

  // ── Navigateur OneDrive ─────────────────────────────────────
  const [browseOpen,   setBrowseOpen]   = React.useState(false);
  const [browseFolder, setBrowseFolder] = React.useState('Blue/Comptabilité/Releve de compte');
  const [browseItems,  setBrowseItems]  = React.useState([]);
  const [browseLoading,setBrowseLoading]= React.useState(false);
  const [browseError,  setBrowseError]  = React.useState('');
  const [folderImporting, setFolderImporting] = React.useState(false);
  // Étape d'assignation : [{filename, transactions, assignedId}] | null
  const [assignPending, setAssignPending] = React.useState(null);

  const browseOneDrive = async (folder) => {
    setBrowseLoading(true); setBrowseError('');
    try {
      const r = await fetch(`/api/reconciliation/onedrive/browse?folder=${encodeURIComponent(folder)}`);
      const d = await r.json();
      if (d.error) { setBrowseError(d.error); return; }
      if (d.warning) setBrowseError(d.warning);
      setBrowseItems(d.items || []);
    } catch(e) { setBrowseError(e.message); }
    finally { setBrowseLoading(false); }
  };

  const openBrowser = () => {
    const MOIS_FR = ['Janvier','Février','Mars','Avril','Mai','Juin',
                     'Juillet','Août','Septembre','Octobre','Novembre','Décembre'];
    const [yr, mo] = selectedMonth.split('-').map(Number);
    const moisDossier = `${String(mo).padStart(2,'0')} - ${MOIS_FR[mo-1]}`;
    const autoFolder  = `Blue/Comptabilité/Releve de compte/${yr}/${moisDossier}`;
    setBrowseFolder(autoFolder);
    setBrowseOpen(true);
    setBrowseItems([]);
    browseOneDrive(autoFolder);
  };

  /** Importe tous les fichiers du dossier courant puis ouvre l'étape d'assignation */
  const importFolder = async () => {
    const files = browseItems.filter(item => !item.isFolder);
    if (!files.length) return;
    setFolderImporting(true);
    const results = [];
    const errors  = [];
    for (const file of files) {
      try {
        const r = await fetch('/api/reconciliation/import-statement', {
          method:'POST', headers:{'Content-Type':'application/json'},
          body: JSON.stringify({ itemId: file.id, filename: file.name }),
        });
        const d = await r.json();
        if (d.error) { errors.push(`${file.name} : ${d.error}`); continue; }
        if (!d.transactions?.length) { errors.push(`${file.name} : aucune transaction extraite`); continue; }
        // Auto-suggestion : cherche si le nom d'un programme apparaît dans le nom du fichier
        const nl = file.name.toLowerCase();
        let suggestedId = null; // null = SAS par défaut
        for (const prog of progAccounts) {
          const token = (prog.ville || prog.nom || '').toLowerCase().split(/[\s\-–]/)[0];
          if (token.length >= 3 && nl.includes(token)) { suggestedId = prog.id; break; }
        }
        results.push({ filename: file.name, transactions: d.transactions, assignedId: suggestedId });
      } catch(e) { errors.push(`${file.name} : ${e.message}`); }
    }
    setFolderImporting(false);
    if (results.length) {
      if (errors.length) alert(`⚠ ${errors.length} fichier(s) ignoré(s) :\n${errors.join('\n')}`);
      setBrowseOpen(false);
      setAssignPending(results);
    } else {
      alert(`Échec de l'import. Détails :\n\n${errors.join('\n')}`);
    }
  };

  /** Sauvegarde les relevés après assignation */
  const confirmAssignments = (assignments) => {
    const assignedProgIds = new Set(assignments.map(a => a.assignedId));
    setRecon(r => ({
      ...r,
      statements: [
        ...(r.statements||[]).filter(s => !assignedProgIds.has(s.programmeId)),
        ...assignments.map(a => {
          const prog = a.assignedId === null ? null : projects.find(p => p.id === a.assignedId);
          return {
            id: uid(), programmeId: a.assignedId,
            compteLabel: a.assignedId === null ? 'Compte principal SAS'
              : (prog ? (prog.ville ? prog.ville+' – '+prog.nom : prog.nom) : 'Programme'),
            filename: a.filename,
            importedAt: new Date().toISOString(),
            transactions: a.transactions,
          };
        }),
      ],
    }));
    setAssignPending(null);
  };

  // ── Lignes plan hebdo pour le mois sélectionné ───────────────
  // On inclut les semaines dont le lundi tombe dans le mois (strict).
  // La tolérance de date du matching (±10j) couvre les petits décalages
  // entre la date planifiée et la date réelle de la transaction bancaire.
  const planLines = React.useMemo(() => {
    const [yr, mo] = selectedMonth.split('-').map(Number);
    const bufStart = new Date(yr, mo-1, 1).getTime();   // 1er du mois
    const bufEnd   = new Date(yr, mo,   0).getTime();   // dernier jour du mois
    const out = [];
    rows.forEach(row => {
      const proj = projects.find(p => p.id === row.projetId);
      if (!proj) return;
      values.forEach(v => {
        if (v.rowId !== row.id || !v.montant) return;
        const t = new Date(v.weekIso).getTime();
        if (t < bufStart || t > bufEnd) return;
        out.push({
          rowId:       row.id,
          weekIso:     v.weekIso,
          montant:     v.montant,
          programmeId: proj.isGlobal ? null : proj.id,
          rowLabel:    row.label,
          programmeNom: proj.isGlobal ? 'Global SAS'
                        : (proj.ville ? proj.ville+' – '+proj.nom : proj.nom),
          isGlobal:    !!proj.isGlobal,
          statut:      row.statut,
        });
      });
    });
    return out.sort((a,b) => a.weekIso.localeCompare(b.weekIso));
  }, [selectedMonth, rows, values, projects]);

  // ── Auto-match ──────────────────────────────────────────────
  const [autoMatchLoading, setAutoMatchLoading] = React.useState(false);
  const autoMatch = async () => {
    if (!(recon.statements||[]).length) {
      alert('Importez au moins un relevé bancaire avant de lancer le rapprochement automatique.'); return;
    }
    setAutoMatchLoading(true);
    try {
      const r = await fetch('/api/reconciliation/auto-match', {
        method:'POST', headers:{'Content-Type':'application/json'},
        body: JSON.stringify({ planLines, statements: recon.statements }),
      });
      const d = await r.json();
      if (d.error) { alert('Erreur auto-match : '+d.error); return; }
      setRecon(r => ({ ...r, matches: d.matches }));
    } catch(e) { alert('Erreur : '+e.message); }
    finally { setAutoMatchLoading(false); }
  };

  // ── Matching manuel ─────────────────────────────────────────
  const [selectedPlan, setSelectedPlan] = React.useState(null); // {key,rowId,weekIso}
  // Mode N:M manuel
  const [nmMode,     setNmMode]     = React.useState(false);
  const [nmSelPlan,  setNmSelPlan]  = React.useState(new Set()); // Set<"rowId|weekIso">
  const [nmSelTxn,   setNmSelTxn]   = React.useState(new Set()); // Set<txnId>

  const createManualMatch = (planRowId, planWeekIso, txnId, stmtId) => {
    const pl   = planLines.find(p => p.rowId===planRowId && p.weekIso===planWeekIso);
    const stmt = (recon.statements||[]).find(s => s.id===stmtId);
    const txn  = stmt?.transactions?.find(t => t.id===txnId);
    if (!pl || !txn) return;
    setRecon(r => ({
      ...r,
      matches: [...(r.matches||[]), {
        id:  uid(),
        planRowId, planWeekIso, planMontant: pl.montant,
        bankTransactionId: txnId, bankStatementId: stmtId,
        mode: 'manuel',
        deltaJours: Math.round(Math.abs(new Date(planWeekIso)-new Date(txn.date))/86400000),
        crossAccount: pl.programmeId !== stmt.programmeId,
        valide: true, score: 1, notes: '',
      }],
    }));
    setSelectedPlan(null);
  };

  // ── Création groupe N:M manuel ───────────────────────────────
  const createNMGroup = () => {
    if (nmSelPlan.size < 1 || nmSelTxn.size < 1) return;
    const sumPlan = [...nmSelPlan].reduce((s, key) => {
      const [rowId, weekIso] = key.split('|');
      const pl = planLines.find(p => p.rowId===rowId && p.weekIso===weekIso);
      return s + (pl?.montant||0);
    }, 0);
    const sumTxn = [...nmSelTxn].reduce((s, txnId) => {
      for (const stmt of (recon.statements||[])) {
        const txn = stmt.transactions?.find(t => t.id===txnId);
        if (txn) return s + (txn.montant||0);
      }
      return s;
    }, 0);
    const ecart = Math.abs(sumPlan - sumTxn);
    if (ecart > 2 && !confirm(`Écart de ${fmtEur(ecart)} entre la somme plan (${fmtEur(sumPlan)}) et la somme banque (${fmtEur(sumTxn)}). Créer quand même le groupe ?`)) return;
    const groupId = uid();
    const newMatches = [];
    for (const key of nmSelPlan) {
      const [rowId, weekIso] = key.split('|');
      const pl = planLines.find(p => p.rowId===rowId && p.weekIso===weekIso);
      if (!pl) continue;
      newMatches.push({ id: uid(), nmGroupId: groupId, nmType: 'N_to_M', nmRole: 'plan',
        planRowId: rowId, planWeekIso: weekIso, planMontant: pl.montant,
        bankTransactionId: null, bankStatementId: null,
        mode: 'manuel', valide: true, score: 1, notes: '' });
    }
    for (const txnId of nmSelTxn) {
      const stmt = (recon.statements||[]).find(s => s.transactions?.some(t => t.id===txnId));
      if (!stmt) continue;
      newMatches.push({ id: uid(), nmGroupId: groupId, nmType: 'N_to_M', nmRole: 'txn',
        planRowId: null, planWeekIso: null, planMontant: null,
        bankTransactionId: txnId, bankStatementId: stmt.id,
        mode: 'manuel', valide: true, score: 1, notes: '' });
    }
    setRecon(r => ({ ...r, matches: [...(r.matches||[]), ...newMatches] }));
    setNmSelPlan(new Set()); setNmSelTxn(new Set()); setNmMode(false);
  };

  const removeMatch      = id      => setRecon(r => ({ ...r, matches: (r.matches||[]).filter(m => m.id!==id) }));
  const toggleValidate   = id      => setRecon(r => ({ ...r, matches: (r.matches||[]).map(m => m.id===id?{...m,valide:!m.valide}:m) }));
  const removeMatchGroup = groupId => setRecon(r => ({ ...r, matches: (r.matches||[]).filter(m => m.nmGroupId!==groupId) }));
  const deleteTxn = (txnId, stmtId) => {
    const isMatched = (recon.matches||[]).some(m => m.bankTransactionId === txnId);
    if (isMatched && !confirm('Cette transaction est rapprochée. La supprimer enlèvera aussi le rapprochement associé. Continuer ?')) return;
    // Si la txn appartient à un groupe N_to_M, supprimer tout le groupe
    const nmGroupMatch = (recon.matches||[]).find(m => m.bankTransactionId === txnId && m.nmType === 'N_to_M');
    const groupToRemove = nmGroupMatch?.nmGroupId;
    setRecon(r => ({
      ...r,
      statements: (r.statements||[]).map(s => s.id === stmtId
        ? { ...s, transactions: (s.transactions||[]).filter(t => t.id !== txnId) }
        : s),
      matches: groupToRemove
        ? (r.matches||[]).filter(m => m.nmGroupId !== groupToRemove)
        : (r.matches||[]).filter(m => m.bankTransactionId !== txnId),
    }));
  };

  // ── Tooltip libellé ────────────────────────────────────────
  const [tooltip, setTooltip] = React.useState(null); // {text, x, y}

  // ── Édition manuelle d'un montant bancaire ──────────────────
  const [editingMontant, setEditingMontant] = React.useState(null); // {txnId, stmtId, value}
  const updateTxnMontant = (txnId, stmtId, rawValue) => {
    const parsed = parseFloat(String(rawValue).replace(',', '.'));
    if (isNaN(parsed)) { setEditingMontant(null); return; }
    setRecon(r => ({
      ...r,
      statements: (r.statements || []).map(s => s.id === stmtId
        ? { ...s, transactions: (s.transactions || []).map(t => t.id === txnId ? { ...t, montant: parsed } : t) }
        : s),
    }));
    setEditingMontant(null);
  };

  // ── Données dérivées ────────────────────────────────────────
  const [filter, setFilter] = React.useState('all');
  const matchedPlanKeys = new Set((recon.matches||[]).map(m => m.planRowId+'|'+m.planWeekIso));
  const matchedTxnIds   = new Set((recon.matches||[]).map(m => m.bankTransactionId));
  const allTxns = (recon.statements||[]).flatMap(s =>
    (s.transactions||[]).map(t => ({ ...t, statementId:s.id, compteLabel:s.compteLabel }))
  );
  const unmatchedPlan = planLines.filter(pl => !matchedPlanKeys.has(pl.rowId+'|'+pl.weekIso));
  const unmatchedTxns = allTxns.filter(t => !matchedTxnIds.has(t.id));

  const totalPlan   = planLines.length;
  const matchedPlan = matchedPlanKeys.size;
  const totalTxns   = allTxns.length;
  const matchedTxns = matchedTxnIds.size;
  const sommeBancaire = allTxns.reduce((s,t) => s+(t.montant||0), 0);

  // ── Groupes N:M ─────────────────────────────────────────────
  const nmGroups = React.useMemo(() => {
    const groups = {};
    (recon.matches||[]).forEach(m => {
      if (m.nmGroupId) { if (!groups[m.nmGroupId]) groups[m.nmGroupId] = []; groups[m.nmGroupId].push(m); }
    });
    return groups;
  }, [recon.matches]);

  // ── Écart de rapprochement (∑ plan matchés − ∑ banque matchés) ──
  const ecartRapprochement = React.useMemo(() => {
    const ms = recon.matches||[];
    if (!ms.length) return null;
    const sommePlanMatche   = ms.reduce((s,m) => s + (m.planMontant||0), 0);
    const sommeBanqueMatche = ms.reduce((s,m) => {
      const stmt = (recon.statements||[]).find(st => st.id === m.bankStatementId);
      const txn  = stmt?.transactions?.find(t => t.id === m.bankTransactionId);
      return s + (txn?.montant||0);
    }, 0);
    return sommePlanMatche - sommeBanqueMatche;
  }, [recon.matches, recon.statements]);

  // Programmes actifs (non globaux, non abandonnés)
  const progAccounts = projects.filter(p => !p.isGlobal && p.statut!=='Abandonné');

  // Sélecteur de mois (13 derniers mois)
  const monthOptions = React.useMemo(() => {
    const opts = []; const now = new Date();
    for (let i = 0; i < 13; i++) {
      const d = new Date(now.getFullYear(), now.getMonth()-i, 1);
      const val = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}`;
      const lbl = d.toLocaleDateString('fr-FR',{month:'long',year:'numeric'});
      opts.push({ val, lbl: lbl.charAt(0).toUpperCase()+lbl.slice(1) });
    }
    return opts;
  }, []);

  const fmtD = iso => { if(!iso)return''; const[y,m,d]=iso.split('-'); return `${d}/${m}/${y}`; };
  const badge = (bg,color,txt) => (
    <span style={{background:bg,color,borderRadius:10,padding:'2px 8px',fontSize:10,fontWeight:700,whiteSpace:'nowrap'}}>{txt}</span>
  );
  const btnS = (bg,sm) => ({background:bg,color:'#fff',border:'none',borderRadius:7,
    padding:sm?'4px 10px':'7px 14px',cursor:'pointer',fontSize:sm?11:12,fontWeight:600});
  const inp = {background:'#f8fafc',border:'1px solid #cbd5e1',borderRadius:8,color:'#1e293b',
    padding:'7px 11px',fontSize:13,width:'100%'};

  // Construire la liste plate : matches 1:1, groupes N:M, non-matchés plan, non-matchés banque
  const displayRows = [
    // Matches standard 1:1 (sans nmGroupId)
    ...(recon.matches||[])
      .filter(m => !m.nmGroupId)
      .filter(() => filter !== 'unmatched')
      .map(match => {
        const pl   = planLines.find(p => p.rowId===match.planRowId && p.weekIso===match.planWeekIso);
        const stmt = (recon.statements||[]).find(s => s.id===match.bankStatementId);
        const txn  = stmt?.transactions?.find(t => t.id===match.bankTransactionId);
        return { type:'matched', match, pl, txn, stmt };
      }),
    // Groupes N:M (nmGroupId présent)
    ...(filter !== 'unmatched' ? Object.entries(nmGroups).flatMap(([groupId, groupMatches]) => {
      if (!groupMatches.length) return [];
      const nmType = groupMatches[0].nmType;
      if (nmType === '1_to_N') {
        // 1 ligne plan → N transactions
        const pl = planLines.find(p => p.rowId===groupMatches[0].planRowId && p.weekIso===groupMatches[0].planWeekIso);
        return groupMatches.map((match, idx) => {
          const stmt = (recon.statements||[]).find(s => s.id===match.bankStatementId);
          const txn  = stmt?.transactions?.find(t => t.id===match.bankTransactionId);
          return { type:'matched-nm', match, pl:idx===0?pl:null, txn, stmt, groupId, nmType,
            isFirst:idx===0, isLast:idx===groupMatches.length-1, groupSize:groupMatches.length, planRef:pl };
        });
      } else if (nmType === 'N_to_M') {
        // N lignes plan → M transactions (groupe manuel)
        const planRecs = groupMatches.filter(m => m.nmRole === 'plan');
        const txnRecs  = groupMatches.filter(m => m.nmRole === 'txn');
        const maxLen   = Math.max(planRecs.length, txnRecs.length, 1);
        const groupSizeLabel = `${planRecs.length}→${txnRecs.length}`;
        return Array.from({length: maxLen}, (_, idx) => {
          const mPlan = planRecs[idx];
          const mTxn  = txnRecs[idx];
          const pl    = mPlan ? planLines.find(p => p.rowId===mPlan.planRowId && p.weekIso===mPlan.planWeekIso) : null;
          const stmt  = mTxn ? (recon.statements||[]).find(s => s.id===mTxn.bankStatementId) : null;
          const txn   = mTxn ? stmt?.transactions?.find(t => t.id===mTxn.bankTransactionId) : null;
          return { type:'matched-nm', match: mPlan||mTxn, pl, txn, stmt,
            groupId, nmType, isFirst:idx===0, isLast:idx===maxLen-1, groupSize:groupSizeLabel, planRef:pl };
        });
      } else {
        // N lignes plan → 1 transaction
        const stmt = (recon.statements||[]).find(s => s.id===groupMatches[0].bankStatementId);
        const txn  = stmt?.transactions?.find(t => t.id===groupMatches[0].bankTransactionId);
        return groupMatches.map((match, idx) => {
          const pl = planLines.find(p => p.rowId===match.planRowId && p.weekIso===match.planWeekIso);
          return { type:'matched-nm', match, pl, txn:idx===0?txn:null, stmt:idx===0?stmt:null,
            groupId, nmType, isFirst:idx===0, isLast:idx===groupMatches.length-1, groupSize:groupMatches.length, planRef:pl };
        });
      }
    }) : []),
    ...(filter !== 'matched' ? unmatchedPlan.map(pl => ({ type:'unmatched-plan', pl })) : []),
    ...(filter !== 'matched' ? unmatchedTxns.map(txn => ({ type:'unmatched-txn', txn })) : []),
  ];

  return (
    <React.Fragment>
    <div style={{padding:'16px 20px',maxWidth:1400,margin:'0 auto'}}>

      {/* ── Barre d'actions ── */}
      <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:18,flexWrap:'wrap'}}>
        <span style={{fontWeight:800,fontSize:15}}>⚖️ Réconciliation bancaire</span>
        <select value={selectedMonth}
          onChange={e=>{setSelectedMonth(e.target.value);setSelectedPlan(null);}}
          style={{...inp,width:'auto',padding:'6px 12px',fontWeight:600}}>
          {monthOptions.map(o=><option key={o.val} value={o.val}>{o.lbl}</option>)}
        </select>
        <button onClick={autoMatch} disabled={autoMatchLoading} style={btnS('#2563eb',true)}>
          {autoMatchLoading?'⏳ Calcul…':'🔄 Auto-match'}
        </button>
        <button onClick={()=>setRecon(r=>({...r,matches:[]}))}
          style={btnS('#dc2626',true)} title="Réinitialiser tous les rapprochements du mois">
          🗑 Effacer matches
        </button>
        {badge(
          recon.status==='terminée'?'#dcfce7':'#fef9c3',
          recon.status==='terminée'?'#15803d':'#713f12',
          recon.status==='terminée'?'✓ Terminée':'● Brouillon'
        )}
        {recon.status!=='terminée'
          ? <button onClick={()=>setRecon(r=>({...r,status:'terminée'}))} style={btnS('#059669',true)}>✓ Marquer terminée</button>
          : <button onClick={()=>setRecon(r=>({...r,status:'brouillon'}))} style={btnS('#64748b',true)}>↩ Rouvrir</button>
        }
      </div>

      {/* ── Import des relevés ── */}
      <div style={{background:'#fff',borderRadius:10,border:'1px solid #e2e8f0',padding:16,marginBottom:14}}>
        <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:12,flexWrap:'wrap'}}>
          <div style={{fontWeight:700,fontSize:13}}>📁 Relevés bancaires</div>
          <button onClick={openBrowser} style={btnS('#2563eb',true)}>
            📂 Parcourir OneDrive…
          </button>
          {(recon.statements||[]).length>0&&(
            <button onClick={()=>{
              const matchCount=(recon.matches||[]).length;
              const msg=matchCount>0
                ? `Supprimer tous les relevés du mois et leurs ${matchCount} rapprochement(s) ?`
                : 'Supprimer tous les relevés du mois ?';
              if(confirm(msg)) setRecon(r=>({...r,statements:[],matches:[]}));
            }} style={btnS('#dc2626',true)} title="Supprimer tous les relevés et leurs matches">
              🗑 Effacer relevés
            </button>
          )}
        </div>
        {(recon.statements||[]).length===0 ? (
          <div style={{color:'#94a3b8',fontSize:13,padding:'8px 0'}}>
            Cliquez « Parcourir OneDrive » pour naviguer jusqu'au dossier du mois et importer tous les relevés d'un coup.
          </div>
        ) : (
          <div style={{display:'flex',flexDirection:'column',gap:6}}>
            {(recon.statements||[]).map(stmt=>{
              // Plage de dates des transactions pour vérification visuelle
              const dates = (stmt.transactions||[]).map(t=>t.date).filter(Boolean).sort();
              const dateRange = dates.length
                ? (dates[0]===dates[dates.length-1] ? fmtD(dates[0]) : `${fmtD(dates[0])} → ${fmtD(dates[dates.length-1])}`)
                : null;
              // Matches associés à ce relevé
              const stmtMatchCount = (recon.matches||[]).filter(m=>m.bankStatementId===stmt.id).length;
              return (
                <div key={stmt.id} style={{borderRadius:8,border:'1px solid #e2e8f0',background:'#f0fdf4',overflow:'hidden'}}>
                  <div style={{display:'flex',alignItems:'center',gap:10,padding:'8px 12px'}}>
                    <span style={{fontSize:15}}>{stmt.filename?.toLowerCase().endsWith('.pdf')?'📄':'📊'}</span>
                    <div style={{flex:1,minWidth:0}}>
                      <div style={{fontWeight:600,fontSize:13}}>{stmt.compteLabel}</div>
                      <div style={{color:'#64748b',fontSize:11,display:'flex',gap:8,flexWrap:'wrap'}}>
                        <span>{stmt.filename}</span>
                        <span>·</span>
                        <span>{stmt.transactions?.length||0} opérations</span>
                        {dateRange&&<><span>·</span><span style={{color: '#0284c7'}}>📅 {dateRange}</span></>}
                        {stmtMatchCount>0&&<><span>·</span><span style={{color:'#15803d'}}>⚖️ {stmtMatchCount} match{stmtMatchCount>1?'s':''}</span></>}
                      </div>
                    </div>
                    {/* Réassigner le compte */}
                    <select
                      value={stmt.programmeId||''}
                      title="Réassigner ce relevé à un autre compte"
                      onChange={e=>{
                        const newId = e.target.value || null;
                        const prog = newId===null ? null : projects.find(p=>p.id===newId);
                        const newLabel = newId===null ? 'Compte principal SAS'
                          : (prog ? (prog.ville?prog.ville+' – '+prog.nom:prog.nom) : 'Programme');
                        setRecon(r=>({...r,
                          statements:(r.statements||[]).map(s=>s.id===stmt.id
                            ? {...s, programmeId:newId, compteLabel:newLabel} : s),
                          // Les matches existants restent valides (même transactions)
                        }));
                      }}
                      style={{background:'#fff',border:'1px solid #cbd5e1',borderRadius:6,
                        color:'#1e293b',padding:'4px 6px',fontSize:11,maxWidth:160,cursor:'pointer'}}>
                      <option value="">🏢 SAS principal</option>
                      {progAccounts.map(p=>(
                        <option key={p.id} value={p.id}>📍 {p.ville?p.ville+' – '+p.nom:p.nom}</option>
                      ))}
                    </select>
                    {/* Supprimer : nettoie aussi les matches */}
                    <button
                      title={stmtMatchCount>0
                        ? `Supprimer ce relevé et ses ${stmtMatchCount} match(s) associé(s)`
                        : 'Supprimer ce relevé'}
                      onClick={()=>{
                        const msg = stmtMatchCount>0
                          ? `Supprimer "${stmt.filename}" et ses ${stmtMatchCount} rapprochement(s) associé(s) ?`
                          : `Supprimer "${stmt.filename}" ?`;
                        if (!confirm(msg)) return;
                        setRecon(r=>({...r,
                          statements:(r.statements||[]).filter(s=>s.id!==stmt.id),
                          matches:(r.matches||[]).filter(m=>m.bankStatementId!==stmt.id),
                        }));
                      }}
                      style={{background:'none',border:'none',cursor:'pointer',
                        color: stmtMatchCount>0?'#dc2626':'#94a3b8',fontSize:16,flexShrink:0}}
                    >✕</button>
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </div>

      {/* ── Modal navigateur OneDrive ── */}
      {browseOpen&&(()=>{
        const navigateTo = (folder) => { setBrowseFolder(folder); browseOneDrive(folder); };
        const navigateUp = () => {
          const parts = browseFolder.split('/').filter(Boolean);
          if (parts.length > 1) navigateTo(parts.slice(0,-1).join('/'));
        };
        const navigateInto = (folderName) => navigateTo(browseFolder.replace(/\/+$/,'')+'/'+folderName);
        const sortedItems = [...browseItems].sort((a,b)=>{
          if (a.isFolder!==b.isFolder) return a.isFolder?-1:1;
          return a.name.localeCompare(b.name,'fr');
        });
        const crumbs = browseFolder.split('/').filter(Boolean);
        const fileCount = browseItems.filter(i=>!i.isFolder).length;
        return (
          <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.5)',zIndex:1000,display:'flex',alignItems:'center',justifyContent:'center'}}>
            <div style={{background:'#fff',borderRadius:12,padding:24,width:600,maxHeight:'82vh',display:'flex',flexDirection:'column',gap:10,boxShadow:'0 20px 60px rgba(0,0,0,0.2)'}}>
              <div style={{fontWeight:700,fontSize:15}}>📂 Sélectionner le dossier du mois</div>

              {/* Fil d'Ariane */}
              <div style={{display:'flex',alignItems:'center',gap:4,flexWrap:'wrap',background:'#f8fafc',borderRadius:7,padding:'6px 10px',border:'1px solid #e2e8f0'}}>
                {crumbs.map((seg,i)=>(
                  <React.Fragment key={i}>
                    {i>0&&<span style={{color:'#94a3b8',fontSize:12}}>/</span>}
                    <button onClick={()=>navigateTo(crumbs.slice(0,i+1).join('/'))}
                      style={{background:'none',border:'none',cursor:'pointer',
                        color:i===crumbs.length-1?'#1e293b':'#2563eb',
                        fontSize:12,fontWeight:i===crumbs.length-1?700:400,padding:'1px 3px',borderRadius:4}}>
                      {seg}
                    </button>
                  </React.Fragment>
                ))}
                {browseLoading&&<span style={{color:'#94a3b8',fontSize:11,marginLeft:4}}>⏳</span>}
              </div>

              {/* Barre chemin + retour */}
              <div style={{display:'flex',gap:8}}>
                {crumbs.length>1&&(
                  <button onClick={navigateUp} style={btnS('#64748b',true)}>← Retour</button>
                )}
                <input value={browseFolder} onChange={e=>setBrowseFolder(e.target.value)}
                  placeholder="Chemin OneDrive" style={{...inp,flex:1,fontSize:12}}
                  onKeyDown={e=>e.key==='Enter'&&navigateTo(browseFolder)}/>
                <button onClick={()=>navigateTo(browseFolder)} style={btnS('#334155',true)} disabled={browseLoading}>🔍</button>
              </div>

              {browseError&&<div style={{color:'#dc2626',fontSize:12,background:'#fee2e2',borderRadius:6,padding:'6px 10px'}}>⚠ {browseError}</div>}

              <div style={{overflowY:'auto',flex:1,minHeight:80,display:'flex',flexDirection:'column',gap:3}}>
                {sortedItems.length===0&&!browseLoading&&(
                  <div style={{color:'#64748b',fontSize:13,padding:24,textAlign:'center'}}>Dossier vide</div>
                )}
                {sortedItems.map(item=>(
                  <div key={item.id}
                    style={{padding:'8px 12px',borderRadius:7,display:'flex',alignItems:'center',gap:10,
                      border:'1px solid #e2e8f0',background:item.isFolder?'#f0f9ff':'#f8fafc',
                      cursor:'pointer',transition:'background 0.1s'}}
                    onClick={()=>item.isFolder&&navigateInto(item.name)}
                    onMouseEnter={e=>e.currentTarget.style.background=item.isFolder?'#dbeafe':'#f1f5f9'}
                    onMouseLeave={e=>e.currentTarget.style.background=item.isFolder?'#f0f9ff':'#f8fafc'}>
                    <span style={{fontSize:16}}>{item.isFolder?'📁':item.name.toLowerCase().endsWith('.pdf')?'📄':'📊'}</span>
                    <div style={{flex:1,minWidth:0}}>
                      <div style={{fontWeight:item.isFolder?700:600,fontSize:13,overflow:'hidden',textOverflow:'ellipsis',
                        whiteSpace:'nowrap',color:item.isFolder?'#1d4ed8':'#1e293b'}}>{item.name}</div>
                      {!item.isFolder&&item.lastModified&&(
                        <div style={{color:'#64748b',fontSize:11}}>
                          {new Date(item.lastModified).toLocaleDateString('fr-FR')}
                          {item.size?` · ${Math.round(item.size/1024)} Ko`:''}
                        </div>
                      )}
                    </div>
                    <span style={{color:'#94a3b8',fontSize:13}}>{item.isFolder?'›':''}</span>
                  </div>
                ))}
              </div>

              {/* Pied : importer tout ou fermer */}
              <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',paddingTop:4,borderTop:'1px solid #e2e8f0'}}>
                <div style={{color:'#64748b',fontSize:12}}>
                  {fileCount>0
                    ? `${fileCount} fichier${fileCount>1?'s':''} dans ce dossier`
                    : 'Naviguez jusqu\'au dossier du mois'}
                </div>
                <div style={{display:'flex',gap:8}}>
                  <button onClick={()=>setBrowseOpen(false)} style={btnS('#64748b',true)}>✕ Fermer</button>
                  {fileCount>0&&(
                    <button onClick={importFolder} disabled={folderImporting} style={btnS('#059669',false)}>
                      {folderImporting?'⏳ Import en cours…':`📥 Importer ce dossier (${fileCount} fichier${fileCount>1?'s':''})`}
                    </button>
                  )}
                </div>
              </div>
            </div>
          </div>
        );
      })()}

      {/* ── Modal assignation des relevés aux comptes ── */}
      {assignPending&&(()=>{
        const selInp = {background:'#f8fafc',border:'1px solid #cbd5e1',borderRadius:6,color:'#1e293b',
          padding:'5px 8px',fontSize:12,width:'100%'};
        return (
          <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.5)',zIndex:1001,display:'flex',alignItems:'center',justifyContent:'center'}}>
            <div style={{background:'#fff',borderRadius:12,padding:24,width:560,maxHeight:'80vh',display:'flex',flexDirection:'column',gap:14,boxShadow:'0 20px 60px rgba(0,0,0,0.2)'}}>
              <div style={{fontWeight:700,fontSize:15}}>🏦 Assigner les relevés aux comptes</div>
              <div style={{color:'#64748b',fontSize:12}}>
                Pour chaque fichier importé, indiquez à quel compte il correspond. Le compte SAS est suggéré par défaut.
              </div>
              <div style={{overflowY:'auto',flex:1,display:'flex',flexDirection:'column',gap:8}}>
                {assignPending.map((item,i)=>(
                  <div key={i} style={{display:'flex',alignItems:'center',gap:10,padding:'10px 12px',borderRadius:8,border:'1px solid #e2e8f0',background:'#f8fafc'}}>
                    <span style={{fontSize:15}}>{item.filename.toLowerCase().endsWith('.pdf')?'📄':'📊'}</span>
                    <div style={{flex:1,minWidth:0}}>
                      <div style={{fontWeight:600,fontSize:12,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{item.filename}</div>
                      <div style={{color:'#64748b',fontSize:11}}>{item.transactions.length} opérations</div>
                    </div>
                    <select value={item.assignedId||''} style={{...selInp,width:180,flexShrink:0}}
                      onChange={e=>{
                        const v=e.target.value||null;
                        setAssignPending(prev=>prev.map((x,j)=>j===i?{...x,assignedId:v}:x));
                      }}>
                      <option value="">🏢 Compte principal SAS</option>
                      {progAccounts.map(p=>(
                        <option key={p.id} value={p.id}>📍 {p.ville?p.ville+' – '+p.nom:p.nom}</option>
                      ))}
                    </select>
                  </div>
                ))}
              </div>
              <div style={{display:'flex',justifyContent:'flex-end',gap:8}}>
                <button onClick={()=>setAssignPending(null)} style={btnS('#64748b',true)}>✕ Annuler</button>
                <button onClick={()=>confirmAssignments(assignPending)} style={btnS('#059669',false)}>
                  ✓ Confirmer et enregistrer
                </button>
              </div>
            </div>
          </div>
        );
      })()}

      {/* ── Tableau de rapprochement ── */}
      {(planLines.length>0||allTxns.length>0)&&(
        <div style={{background:'#fff',borderRadius:10,border:'1px solid #e2e8f0',padding:16,marginBottom:14}}>
          <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:14,flexWrap:'wrap'}}>
            <div style={{fontWeight:700,fontSize:13}}>📊 Tableau de rapprochement</div>
            {['all','unmatched','matched'].map(f=>(
              <button key={f} onClick={()=>setFilter(f)}
                style={{background:filter===f?'#2563eb':'none',color:filter===f?'#fff':'#64748b',
                  border:'1px solid '+(filter===f?'#2563eb':'#e2e8f0'),borderRadius:6,
                  padding:'3px 10px',fontSize:11,cursor:'pointer',fontWeight:filter===f?700:400}}>
                {f==='all'?'Tout':f==='unmatched'?'Non matchés':'Matchés'}
              </button>
            ))}
            <button
              onClick={()=>{setNmMode(m=>!m);setNmSelPlan(new Set());setNmSelTxn(new Set());setSelectedPlan(null);}}
              style={{...btnS(nmMode?'#7c3aed':'#64748b',true),outline:nmMode?'2px solid #a78bfa':'none',outlineOffset:1}}>
              ⊞ Mode N:M{nmMode?' actif':''}
            </button>
            {nmMode&&(()=>{
              const sumP = [...nmSelPlan].reduce((s,key)=>{
                const [rowId,weekIso]=key.split('|');
                const pl=planLines.find(p=>p.rowId===rowId&&p.weekIso===weekIso);
                return s+(pl?.montant||0);
              },0);
              const sumT = [...nmSelTxn].reduce((s,txnId)=>{
                for(const stmt of (recon.statements||[])){
                  const txn=stmt.transactions?.find(t=>t.id===txnId);
                  if(txn) return s+(txn.montant||0);
                }
                return s;
              },0);
              const ecart = sumP - sumT;
              const balanced = Math.abs(ecart) <= 2;
              return (
                <span style={{fontSize:12,background:'#f3e8ff',color:'#7c3aed',padding:'4px 10px',borderRadius:6,display:'flex',gap:8,alignItems:'center',flexWrap:'wrap'}}>
                  <span style={{display:'flex',gap:4,alignItems:'center'}}>
                    <span style={{color:'#94a3b8'}}>Plan :</span>
                    <span style={{fontWeight:700,color:nmSelPlan.size?'#7c3aed':'#cbd5e1'}}>
                      {nmSelPlan.size?fmtEur(sumP):'—'}
                    </span>
                    <span style={{color:'#cbd5e1',margin:'0 2px'}}>·</span>
                    <span style={{color:'#94a3b8'}}>Banque :</span>
                    <span style={{fontWeight:700,color:nmSelTxn.size?'#7c3aed':'#cbd5e1'}}>
                      {nmSelTxn.size?fmtEur(sumT):'—'}
                    </span>
                    {nmSelPlan.size>0&&nmSelTxn.size>0&&(
                      <span style={{
                        marginLeft:4,fontWeight:700,fontSize:11,
                        background:balanced?'#dcfce7':'#fee2e2',
                        color:balanced?'#15803d':'#dc2626',
                        borderRadius:5,padding:'1px 6px',
                      }}>
                        {balanced?'✓ équilibré':`écart ${ecart>0?'+':''}${fmtEur(ecart)}`}
                      </span>
                    )}
                  </span>
                  <span style={{color:'#cbd5e1',fontSize:10}}>{nmSelPlan.size} plan · {nmSelTxn.size} txn</span>
                  {nmSelPlan.size>=1&&nmSelTxn.size>=1&&(
                    <button onClick={createNMGroup}
                      style={{background:'#7c3aed',color:'#fff',border:'none',borderRadius:5,padding:'2px 8px',cursor:'pointer',fontSize:11,fontWeight:700}}>
                      ✓ Créer groupe ({nmSelPlan.size}→{nmSelTxn.size})
                    </button>
                  )}
                  <button onClick={()=>{setNmSelPlan(new Set());setNmSelTxn(new Set());}}
                    style={{background:'none',border:'none',cursor:'pointer',color:'#dc2626',fontWeight:700,fontSize:13}}>✕</button>
                </span>
              );
            })()}
            {!nmMode&&selectedPlan&&(
              <span style={{fontSize:12,background:'#dbeafe',color:'#1d4ed8',padding:'4px 10px',borderRadius:6,display:'flex',gap:8,alignItems:'center'}}>
                ↙ Cliquez une transaction bancaire pour créer le match
                <button onClick={()=>setSelectedPlan(null)} style={{background:'none',border:'none',cursor:'pointer',color:'#dc2626',fontWeight:700,fontSize:13}}>✕</button>
              </span>
            )}
          </div>

          <div style={{overflowX:'auto',overflowY:'auto',maxHeight:'calc(100vh - 260px)'}}>
            <table style={{width:'100%',borderCollapse:'collapse',fontSize:12}}>
              <thead>
                <tr style={{color:'#64748b',textTransform:'uppercase',letterSpacing:0.3,fontSize:10,fontWeight:700}}>
                  <th style={{padding:'8px 10px',textAlign:'left',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Programme</th>
                  <th style={{padding:'8px 10px',textAlign:'left',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Ligne</th>
                  <th style={{padding:'8px 10px',textAlign:'left',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Semaine</th>
                  <th style={{padding:'8px 10px',textAlign:'right',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Montant plan</th>
                  <th style={{padding:'8px 10px',textAlign:'center',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5,width:110}}>Match</th>
                  <th style={{padding:'8px 10px',textAlign:'left',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Date banque</th>
                  <th style={{padding:'8px 10px',textAlign:'left',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Libellé</th>
                  <th style={{padding:'8px 10px',textAlign:'right',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Montant banque</th>
                  <th style={{padding:'8px 10px',textAlign:'left',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5}}>Compte</th>
                  <th style={{padding:'8px 10px',textAlign:'center',borderBottom:'2px solid #e2e8f0',background:'#f8fafc',position:'sticky',top:0,zIndex:5,width:70}}>Action</th>
                </tr>
              </thead>
              <tbody>
                {displayRows.map((item,idx)=>{
                  if (item.type==='matched') {
                    const {match,pl,txn,stmt}=item;
                    return (
                      <tr key={match.id} style={{background:match.valide?'#f0fdf4':'#fffbeb',borderBottom:'1px solid #e2e8f0'}}>
                        <td style={{padding:'7px 10px',fontWeight:600}}>{pl?.programmeNom||'—'}</td>
                        <td style={{padding:'7px 10px',color:'#334155'}}>{pl?.rowLabel||'—'}</td>
                        <td style={{padding:'7px 10px',color:'#64748b'}}>{pl?fmtD(pl.weekIso):'—'}</td>
                        <td style={{padding:'7px 10px',textAlign:'right',fontWeight:700,color:pl&&pl.montant<0?'#dc2626':'#15803d'}}>{pl?fmtEur(pl.montant):'—'}</td>
                        <td style={{padding:'7px 10px',textAlign:'center'}}>
                          <div style={{display:'flex',flexDirection:'column',alignItems:'center',gap:3}}>
                            {badge(match.valide?'#dcfce7':'#fef9c3',match.valide?'#15803d':'#713f12',match.valide?(match.mode==='auto'?'🔄 auto':'✋ manuel'):'⚠ à valider')}
                            {match.deltaJours>0&&<span style={{fontSize:9,color:'#64748b'}}>Δ{match.deltaJours}j</span>}
                            {match.crossAccount&&badge('#ffedd5','#c2410c','SAS↔prog')}
                          </div>
                        </td>
                        <td style={{padding:'7px 10px',color:'#64748b'}}>{txn?fmtD(txn.date):'—'}</td>
                        <td style={{padding:'7px 10px',maxWidth:220,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',color:'#334155',cursor:txn?.libelle?'help':'default'}}
                          onMouseEnter={e=>txn?.libelle&&setTooltip({text:txn.libelle,x:e.clientX,y:e.clientY})}
                          onMouseMove={e=>setTooltip(prev=>prev?{...prev,x:e.clientX,y:e.clientY}:null)}
                          onMouseLeave={()=>setTooltip(null)}>
                          {txn?.libelle||'—'}
                        </td>
                        <td style={{padding:'7px 10px',textAlign:'right',fontWeight:700,color:txn&&txn.montant<0?'#dc2626':'#15803d',cursor:txn?'pointer':undefined}}
                          title={txn?'Double-cliquer pour corriger le montant':undefined}
                          onDoubleClick={()=>txn&&setEditingMontant({txnId:txn.id,stmtId:stmt.id,value:String(txn.montant)})}>
                          {editingMontant?.txnId===txn?.id
                            ? <input autoFocus type="text" value={editingMontant.value}
                                onChange={e=>setEditingMontant(m=>({...m,value:e.target.value}))}
                                onBlur={()=>updateTxnMontant(txn.id,stmt.id,editingMontant.value)}
                                onKeyDown={e=>{if(e.key==='Enter')updateTxnMontant(txn.id,stmt.id,editingMontant.value);if(e.key==='Escape')setEditingMontant(null);}}
                                style={{width:80,textAlign:'right',border:'1px solid #2563eb',borderRadius:4,padding:'2px 5px',fontSize:12,fontWeight:700}}
                                onClick={e=>e.stopPropagation()}/>
                            : txn?fmtEur(txn.montant):'—'}
                        </td>
                        <td style={{padding:'7px 10px',color:'#64748b',fontSize:11}}>{stmt?.compteLabel||'—'}</td>
                        <td style={{padding:'7px 10px',textAlign:'center'}}>
                          <div style={{display:'flex',gap:3,justifyContent:'center'}}>
                            {!match.valide&&<button onClick={()=>toggleValidate(match.id)} style={{fontSize:10,background:'#dcfce7',color:'#15803d',border:'none',borderRadius:4,padding:'2px 5px',cursor:'pointer'}}>✓</button>}
                            <button onClick={()=>removeMatch(match.id)} style={{fontSize:10,background:'#fee2e2',color:'#dc2626',border:'none',borderRadius:4,padding:'2px 5px',cursor:'pointer'}}>✕</button>
                          </div>
                        </td>
                      </tr>
                    );
                  }
                  if (item.type==='matched-nm') {
                    const {match,pl,txn,stmt,groupId,nmType,isFirst,isLast,groupSize,planRef}=item;
                    const rowBg = match.valide?'#f0fdf4':'#fffbeb';
                    return (
                      <tr key={match.id} style={{background:rowBg,borderBottom:isLast?'1px solid #e2e8f0':'1px dashed #d1fae5'}}>
                        <td style={{padding:'7px 10px',fontWeight:pl?600:400,color:pl?'inherit':'#94a3b8'}}>
                          {pl ? pl.programmeNom : <span style={{paddingLeft:14,fontSize:11}}>└─</span>}
                        </td>
                        <td style={{padding:'7px 10px',color:'#334155'}}>{pl ? pl.rowLabel : ''}</td>
                        <td style={{padding:'7px 10px',color:'#64748b'}}>{pl ? fmtD(pl.weekIso) : ''}</td>
                        <td style={{padding:'7px 10px',textAlign:'right',fontWeight:700,color:pl&&pl.montant<0?'#dc2626':'#15803d'}}>
                          {pl ? fmtEur(pl.montant) : ''}
                        </td>
                        <td style={{padding:'7px 10px',textAlign:'center'}}>
                          {isFirst&&<div style={{display:'flex',flexDirection:'column',alignItems:'center',gap:3}}>
                            {badge(
                              match?.valide!==false?'#dcfce7':'#fffbeb',
                              match?.valide!==false?'#15803d':'#713f12',
                              match?.mode==='auto'?'🔄 auto':'✋ manuel'
                            )}
                            <span style={{fontSize:9,color:'#64748b'}}>
                              {nmType==='1_to_N'?`1→${groupSize} txn`:nmType==='N_to_M'?`N:M ${groupSize} txn`:`${groupSize}→1 txn`}
                            </span>
                            {match?.crossAccount&&badge('#ffedd5','#c2410c','SAS↔prog')}
                          </div>}
                        </td>
                        <td style={{padding:'7px 10px',color:'#64748b'}}>{txn ? fmtD(txn.date) : ''}</td>
                        <td style={{padding:'7px 10px',maxWidth:220,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',color:'#334155',cursor:txn?.libelle?'help':'default'}}
                          onMouseEnter={e=>txn?.libelle&&setTooltip({text:txn.libelle,x:e.clientX,y:e.clientY})}
                          onMouseMove={e=>setTooltip(prev=>prev?{...prev,x:e.clientX,y:e.clientY}:null)}
                          onMouseLeave={()=>setTooltip(null)}>
                          {txn?.libelle||''}
                        </td>
                        <td style={{padding:'7px 10px',textAlign:'right',fontWeight:700,color:txn&&txn.montant<0?'#dc2626':'#15803d',cursor:txn?'pointer':undefined}}
                          title={txn?'Double-cliquer pour corriger le montant':undefined}
                          onDoubleClick={()=>txn&&stmt&&setEditingMontant({txnId:txn.id,stmtId:stmt.id,value:String(txn.montant)})}>
                          {editingMontant?.txnId===txn?.id
                            ? <input autoFocus type="text" value={editingMontant.value}
                                onChange={e=>setEditingMontant(m=>({...m,value:e.target.value}))}
                                onBlur={()=>updateTxnMontant(txn.id,stmt.id,editingMontant.value)}
                                onKeyDown={e=>{if(e.key==='Enter')updateTxnMontant(txn.id,stmt.id,editingMontant.value);if(e.key==='Escape')setEditingMontant(null);}}
                                style={{width:80,textAlign:'right',border:'1px solid #2563eb',borderRadius:4,padding:'2px 5px',fontSize:12,fontWeight:700}}
                                onClick={e=>e.stopPropagation()}/>
                            : txn ? fmtEur(txn.montant) : ''}
                        </td>
                        <td style={{padding:'7px 10px',color:'#64748b',fontSize:11}}>{stmt?.compteLabel||''}</td>
                        <td style={{padding:'7px 10px',textAlign:'center'}}>
                          {isFirst&&<button onClick={()=>removeMatchGroup(groupId)}
                            style={{fontSize:10,background:'#fee2e2',color:'#dc2626',border:'none',borderRadius:4,padding:'2px 5px',cursor:'pointer'}}
                            title="Supprimer ce groupe de rapprochement">✕</button>}
                        </td>
                      </tr>
                    );
                  }
                  if (item.type==='unmatched-plan') {
                    const {pl}=item;
                    const plKey=pl.rowId+'|'+pl.weekIso;
                    const isSel=selectedPlan?.key===plKey;
                    const isNmSel=nmSelPlan.has(plKey);
                    const handleClick = nmMode
                      ? ()=>setNmSelPlan(prev=>{const n=new Set(prev);n.has(plKey)?n.delete(plKey):n.add(plKey);return n;})
                      : ()=>setSelectedPlan(isSel?null:{key:plKey,rowId:pl.rowId,weekIso:pl.weekIso});
                    const rowBg = nmMode?(isNmSel?'#f3e8ff':'#faf5ff'):isSel?'#dbeafe':'#fff';
                    const labelColor = nmMode?(isNmSel?'#7c3aed':'#6d28d9'):isSel?'#1d4ed8':'inherit';
                    return (
                      <tr key={plKey} onClick={handleClick}
                        style={{background:rowBg,cursor:'pointer',borderBottom:'1px solid #e2e8f0',transition:'background 0.1s'}}
                        title={nmMode?'Cochez pour inclure dans le groupe N:M':'Cliquez pour sélectionner, puis cliquez une transaction bancaire'}>
                        <td style={{padding:'7px 10px',fontWeight:600,color:labelColor,display:'flex',alignItems:'center',gap:6}}>
                          {nmMode&&<input type="checkbox" readOnly checked={isNmSel}
                            style={{cursor:'pointer',accentColor:'#7c3aed',flexShrink:0}}/>}
                          {pl.programmeNom}
                        </td>
                        <td style={{padding:'7px 10px'}}>{pl.rowLabel}</td>
                        <td style={{padding:'7px 10px',color:'#64748b'}}>{fmtD(pl.weekIso)}</td>
                        <td style={{padding:'7px 10px',textAlign:'right',fontWeight:700,color:pl.montant<0?'#dc2626':'#15803d'}}>{fmtEur(pl.montant)}</td>
                        <td style={{padding:'7px 10px',textAlign:'center'}}>{badge('#fee2e2','#dc2626','non matchée')}</td>
                        <td colSpan={5} style={{padding:'7px 10px',color:'#94a3b8',fontStyle:'italic',fontSize:11}}>
                          {nmMode?'Cocher pour inclure dans le groupe N:M':'Cliquez pour sélectionner, puis une transaction à droite'}
                        </td>
                      </tr>
                    );
                  }
                  if (item.type==='unmatched-txn') {
                    const {txn}=item;
                    const isNmSel=nmSelTxn.has(txn.id);
                    const handleClick = nmMode
                      ? ()=>setNmSelTxn(prev=>{const n=new Set(prev);n.has(txn.id)?n.delete(txn.id):n.add(txn.id);return n;})
                      : ()=>{if(selectedPlan)createManualMatch(selectedPlan.rowId,selectedPlan.weekIso,txn.id,txn.statementId);};
                    const rowBg = nmMode?(isNmSel?'#f3e8ff':'#faf5ff'):selectedPlan?'#f0fdf4':'#fff';
                    return (
                      <tr key={txn.id}
                        onClick={handleClick}
                        style={{background:rowBg,cursor:(nmMode||selectedPlan)?'pointer':'default',
                          borderBottom:'1px solid #e2e8f0',transition:'background 0.15s'}}
                        title={nmMode?'Cochez pour inclure dans le groupe N:M':selectedPlan?'Cliquer pour matcher avec la ligne sélectionnée':''}>
                        <td colSpan={4} style={{padding:'7px 10px',color:'#94a3b8',fontStyle:'italic',fontSize:11}}>—</td>
                        <td style={{padding:'7px 10px',textAlign:'center'}}>{badge('#fff7ed','#c2410c','non matchée')}</td>
                        <td style={{padding:'7px 10px',color:'#64748b'}}>{fmtD(txn.date)}</td>
                        <td style={{padding:'7px 10px',maxWidth:220,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',cursor:txn.libelle?'help':'default'}}
                          onMouseEnter={e=>txn.libelle&&setTooltip({text:txn.libelle,x:e.clientX,y:e.clientY})}
                          onMouseMove={e=>setTooltip(prev=>prev?{...prev,x:e.clientX,y:e.clientY}:null)}
                          onMouseLeave={()=>setTooltip(null)}>
                          {nmMode&&<input type="checkbox" readOnly checked={isNmSel}
                            style={{marginRight:6,cursor:'pointer',accentColor:'#7c3aed',verticalAlign:'middle'}}/>}
                          {txn.libelle}
                        </td>
                        <td style={{padding:'7px 10px',textAlign:'right',fontWeight:700,color:txn.montant<0?'#dc2626':'#15803d',cursor:'pointer'}}
                          title="Double-cliquer pour corriger le montant"
                          onDoubleClick={e=>{e.stopPropagation();setEditingMontant({txnId:txn.id,stmtId:txn.statementId,value:String(txn.montant)});}}>
                          {editingMontant?.txnId===txn.id
                            ? <input autoFocus type="text" value={editingMontant.value}
                                onChange={e=>setEditingMontant(m=>({...m,value:e.target.value}))}
                                onBlur={()=>updateTxnMontant(txn.id,txn.statementId,editingMontant.value)}
                                onKeyDown={e=>{if(e.key==='Enter')updateTxnMontant(txn.id,txn.statementId,editingMontant.value);if(e.key==='Escape')setEditingMontant(null);}}
                                style={{width:80,textAlign:'right',border:'1px solid #2563eb',borderRadius:4,padding:'2px 5px',fontSize:12,fontWeight:700}}
                                onClick={e=>e.stopPropagation()}/>
                            : fmtEur(txn.montant)}
                        </td>
                        <td style={{padding:'7px 10px',color:'#64748b',fontSize:11}}>{txn.compteLabel}</td>
                        <td style={{padding:'7px 10px',textAlign:'center'}}>
                          {!nmMode&&<button onClick={e=>{e.stopPropagation();deleteTxn(txn.id,txn.statementId);}}
                            style={{fontSize:10,background:'#fee2e2',color:'#dc2626',border:'none',borderRadius:4,padding:'2px 5px',cursor:'pointer',lineHeight:1}}
                            title="Supprimer cette transaction (aberration d'extraction)">🗑</button>}
                        </td>
                      </tr>
                    );
                  }
                  return null;
                })}
                {displayRows.length===0&&(
                  <tr><td colSpan={10} style={{padding:24,textAlign:'center',color:'#94a3b8',fontStyle:'italic'}}>
                    {filter==='matched'?'Aucun match pour ce mois.'
                      :filter==='unmatched'?'Tout est reconcilié ✓'
                      :'Importez les relevés bancaires puis lancez l\'auto-match.'}
                  </td></tr>
                )}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {/* ── Cartes résumé ── */}
      <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fit,minmax(190px,1fr))',gap:12}}>
        {[
          {label:'Lignes plan hebdo matchées', val:`${matchedPlan} / ${totalPlan}`,
           pct:totalPlan?Math.round(matchedPlan/totalPlan*100):0, color:'#2563eb'},
          {label:'Transactions bancaires matchées', val:`${matchedTxns} / ${totalTxns}`,
           pct:totalTxns?Math.round(matchedTxns/totalTxns*100):0, color:'#7c3aed'},
          {label:'Total flux relevés (somme)', val:fmtEur(sommeBancaire), pct:null,
           color:sommeBancaire>=0?'#059669':'#dc2626'},
          {label:'Lignes plan non matchées', val:`${unmatchedPlan.length}`,
           pct:null, color:unmatchedPlan.length===0?'#059669':'#dc2626'},
          {label:'Transactions non matchées', val:`${unmatchedTxns.length}`,
           pct:null, color:unmatchedTxns.length===0?'#059669':'#f59e0b'},
          ...(ecartRapprochement!==null?[{
            label:'Écart plan vs banque (matchés)',
            val:fmtEur(ecartRapprochement),
            pct:null,
            color:Math.abs(ecartRapprochement)<2?'#059669':'#dc2626',
            note:Math.abs(ecartRapprochement)<2?'✓ Équilibré':'⚠ Déséquilibre',
          }]:[]),
        ].map(stat=>(
          <div key={stat.label} style={{background:'#fff',borderRadius:10,border:'1px solid #e2e8f0',padding:16}}>
            <div style={{color:'#64748b',fontSize:11,marginBottom:6}}>{stat.label}</div>
            <div style={{fontWeight:700,fontSize:20,color:stat.color}}>{stat.val}</div>
            {stat.note&&<div style={{fontSize:10,color:stat.color,marginTop:3,fontWeight:600}}>{stat.note}</div>}
            {stat.pct!==null&&(
              <div style={{marginTop:8,background:'#e2e8f0',borderRadius:3,height:4}}>
                <div style={{background:stat.color,borderRadius:3,height:4,width:stat.pct+'%',transition:'width 0.4s'}}/>
              </div>
            )}
          </div>
        ))}
      </div>

    </div>

    {/* ── Tooltip libellé ── */}
    {tooltip&&(
      <div style={{
        position:'fixed',zIndex:9999,
        left:tooltip.x+14,top:tooltip.y-10,
        background:'#1e293b',color:'#f1f5f9',
        borderRadius:8,padding:'7px 12px',
        fontSize:12,maxWidth:420,lineHeight:1.45,
        boxShadow:'0 6px 24px rgba(0,0,0,0.28)',
        pointerEvents:'none',wordBreak:'break-word',
        border:'1px solid #334155',
      }}>
        {tooltip.text}
      </div>
    )}
    </React.Fragment>
  );
}

/* ══════════════════════════════════════════════════════════════
   MainApp
══════════════════════════════════════════════════════════════ */
