/* [NALA_CASHBOOK_V1] Buku Kas Harian — full port dari TRPH index.html [CASHBOOK_V1..V12] 2026-07-03.
   Self-contained: helper apiGet/FMT/Ic sendiri (scope babel terpisah dari script utama index.html).
   Expose: window.CashbookTab, window.CashbookReportTab */
(() => { /* [NALA_CASHBOOK_V1] IIFE — hindari bentrok const useState/Ic/FMT dengan script utama (babel share global scope) */
const {useState, useEffect, useRef, useMemo} = React;
const EmptyState = ({msg})=><div style={{padding:24,textAlign:'center',color:'var(--ink-4)',fontSize:12}}>{msg||'Tidak bisa memuat data. Coba lagi.'}</div>;
const LoadingState = ({label})=><div style={{padding:24,textAlign:'center',color:'var(--ink-4)',fontSize:12}}>{label||'Memuat data…'}</div>;

const apiGet = (url)=>fetch(url,{credentials:'include'}).then(r=>r.ok?r.json():Promise.reject(r));
const FMT = {
  rp:(n)=>'Rp'+Math.round(Number(n)||0).toLocaleString('id-ID'),
  rpM:(n)=>{n=Number(n)||0;if(n>=1e9)return'Rp'+(n/1e9).toFixed(1)+'B';if(n>=1e6)return'Rp'+(n/1e6).toFixed(1)+'M';if(n>=1e3)return'Rp'+(n/1e3).toFixed(0)+'K';return'Rp'+n}
};
/* Ic — subset icon TRPH yang dipakai cashbook (check/close/download/edit/plus/trash) */
const Ic = ({name, size=18}) => {
  const p = {
    download:'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3',
    close:'M18 6L6 18M6 6l12 12',
    plus:'M12 5v14M5 12h14',
    check:'M20 6L9 17l-5-5',
    trash:'M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2',
    edit:'M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4z',
  }[name] || '';
  return <svg viewBox="0 0 24 24" width={size} height={size} fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" style={{flexShrink:0}}><path d={p}/></svg>;
};

/* ---------------- CASHBOOK TAB [CASHBOOK_V1] ---------------- */
const CB_STAFF=['Puput','Tutur','Joni','Ely'];
const CB_CATEGORIES=[
  {value:'room_payment', label:'Kamar Walk-In', pill:'p-room'}, /* [CASHBOOK_V11] renamed: distinguish from early/late add-ons */
  {value:'parkir', label:'Parkir', pill:'p-park'},
  {value:'upgrade_fee', label:'Upgrade fee', pill:'p-upg'},
  {value:'early_checkin', label:'Early Check-In', pill:'p-room'}, /* [CASHBOOK_V11] */
  {value:'late_checkout', label:'Late Check-Out', pill:'p-room'}, /* [CASHBOOK_V11] */
  {value:'income_other', label:'Lain-lain', pill:'p-room'}, /* [CASHBOOK_V3] */
  {value:'expense_bensin', label:'Bensin', pill:'p-out'},
  {value:'expense_supplies', label:'Supplies', pill:'p-out'},
  {value:'expense_other', label:'Pengeluaran lain', pill:'p-out'} /* [CASHBOOK_V3] renamed for distinct label */
];
const CB_METHODS=[
  {value:'cash', label:'Cash'},
  {value:'transfer_bca', label:'TR BCA'},
  {value:'transfer_bsi', label:'TR BSI'},
  {value:'edc', label:'EDC'}
];
const CB_CAT_MAP=Object.fromEntries(CB_CATEGORIES.map(c=>[c.value,c]));
const CB_METHOD_MAP=Object.fromEntries(CB_METHODS.map(m=>[m.value,m]));
const CB_INCOME_CATS=new Set(['room_payment','parkir','upgrade_fee','early_checkin','late_checkout','income_other']); /* [CASHBOOK_V3] [CASHBOOK_V11] +early_checkin,late_checkout */
/* [CASHBOOK_V2] */
const CB_INCOME_CATEGORIES=CB_CATEGORIES.filter(c=>CB_INCOME_CATS.has(c.value));
const CB_EXPENSE_CATEGORIES=CB_CATEGORIES.filter(c=>!CB_INCOME_CATS.has(c.value));
const CB_SHIFT_LABEL={pagi:'Pagi',malam:'Malam'};
/* [CASHBOOK_V3] label maps for report tab */
const CB_CATEGORY_LABEL=Object.fromEntries(CB_CATEGORIES.map(c=>[c.value,c.label]));
const CB_METHOD_LABEL=Object.fromEntries(CB_METHODS.map(m=>[m.value,m.label]));
/* [CASHBOOK_V6] audit display helpers */
const CB_ACTION_LABEL={insert:'Tambah',update:'Ubah',delete:'Hapus'};
const CB_ACTION_COLOR={insert:'#27500A',update:'#854F0B',delete:'#A32D2D'};
const CB_ENTITY_LABEL={cashbook_entry:'Transaksi',cashbook_note:'Catatan'};
const cbAuditDiff=(audit)=>{
  const trunc=(s,n)=>s&&s.length>n?s.slice(0,n)+'…':(s||'');
  if(audit.action==='insert'){
    const a=audit.after_state||{};
    if(audit.entity_type==='cashbook_entry'){
      const amt=Number(a.amount||0).toLocaleString('id-ID');
      return `[${a.category}] ${a.room_number||'—'} Rp${amt} ${a.payment_method||''}`;
    }
    return `Catatan: "${trunc(a.content,60)}"`;
  }
  if(audit.action==='delete'){
    const b=audit.before_state||{};
    if(audit.entity_type==='cashbook_entry'){
      const amt=Number(b.amount||0).toLocaleString('id-ID');
      return `Hapus: [${b.category}] ${b.room_number||'—'} Rp${amt}`;
    }
    return `Hapus catatan: "${trunc(b.content,60)}"`;
  }
  /* update: diff fields */
  const before=audit.before_state||{};
  const after=audit.after_state||{};
  const skip=new Set(['updated_at','created_at']);
  const changes=[];
  Object.keys(after).forEach(k=>{
    if(skip.has(k)) return;
    if(JSON.stringify(before[k])!==JSON.stringify(after[k])){
      changes.push(`${k}: ${before[k]??'∅'} → ${after[k]??'∅'}`);
    }
  });
  return changes.join(', ')||'(tidak ada perubahan)';
};
const formatRp=(val)=>{
  if(val===''||val===null||val===undefined)return'';
  const digits=String(val).replace(/\D/g,'');
  if(!digits)return'';
  return 'Rp'+parseInt(digits,10).toLocaleString('id-ID');
};
const parseRp=(str)=>{
  const digits=String(str).replace(/\D/g,'');
  return digits?parseInt(digits,10):'';
};

/* [CASHBOOK_V4] helpers */
const cbAutoShift=()=>new Date().getHours()<16?'pagi':'malam';
const cbFormatDate=(iso)=>{
  if(!iso) return '';
  const d=new Date(iso+'T00:00:00');
  if(isNaN(d.getTime())) return iso;
  return d.toLocaleDateString('id-ID',{day:'numeric',month:'short',year:'numeric'});
};
const cbGroupByShiftStaff=(entries)=>{
  const groups={};
  entries.forEach(e=>{
    const key=`${e.shift}__${e.staff_name}`;
    if(!groups[key]) groups[key]={shift:e.shift,staff_name:e.staff_name,entries:[],notes:[],firstAt:e.created_at};
    groups[key].entries.push(e);
  });
  return groups;
};
const cbBlocksFromEntriesAndNotes=(entries,notes)=>{
  const groups=cbGroupByShiftStaff(entries||[]);
  /* [CASHBOOK_V7] orphan notes: synthesize empty block kalau shift+staff ga ada di entries */
  (notes||[]).forEach(n=>{
    if(n.shift && n.staff_name){
      const key=`${n.shift}__${n.staff_name}`;
      if(!groups[key]){
        groups[key]={shift:n.shift,staff_name:n.staff_name,entries:[],notes:[],firstAt:n.created_at};
      }
      groups[key].notes.push(n);
    }
  });
  return Object.values(groups).sort((a,b)=>{
    if(a.shift!==b.shift) return a.shift==='pagi'?-1:1;
    return new Date(a.firstAt||0)-new Date(b.firstAt||0);
  });
};
/* [CASHBOOK_V8] sort entries: pemasukan dulu, lalu pengeluaran (preserve created_at order within group) */
const cbIsIncome=(cat)=>CB_INCOME_CATS.has(cat);
/* [CASHBOOK_V10] minus prefix for expense amounts */
const cbAmountDisplay=(entry)=>{
  if(!entry || entry.amount==null) return FMT.rp(0);
  return cbIsIncome(entry.category) ? FMT.rp(entry.amount) : '-'+FMT.rp(entry.amount);
};
const cbSortEntries=(entries)=>{
  if(!entries) return [];
  return [...entries].sort((a,b)=>{
    const aIn=cbIsIncome(a.category);
    const bIn=cbIsIncome(b.category);
    if(aIn!==bIn) return aIn?-1:1;
    return new Date(a.created_at||0)-new Date(b.created_at||0);
  });
};

const cbShiftFooter=(entries)=>{
  /* [CASHBOOK_V7] empty case → friendly placeholder */
  if(!entries || entries.length===0) return 'Tidak ada transaksi';
  const inc=CB_INCOME_CATS;
  const exp=new Set(['expense_bensin','expense_supplies','expense_other']);
  const t={cash:0,transfer_bca:0,transfer_bsi:0,edc:0,outcome:0,kamar:0};
  entries.forEach(e=>{
    if(inc.has(e.category)){
      t[e.payment_method]=(t[e.payment_method]||0)+(e.amount||0);
      if(e.category==='room_payment') t.kamar++;
    } else if(exp.has(e.category)){
      t.outcome+=(e.amount||0);
    }
  });
  const parts=[];
  if(t.cash>0) parts.push('Cash '+FMT.rp(t.cash));
  if(t.transfer_bca>0) parts.push('TR BCA '+FMT.rp(t.transfer_bca));
  if(t.transfer_bsi>0) parts.push('TR BSI '+FMT.rp(t.transfer_bsi));
  if(t.edc>0) parts.push('EDC '+FMT.rp(t.edc));
  if(t.outcome>0) parts.push('Pengeluaran '+FMT.rp(t.outcome));
  if(t.kamar>0) parts.push(t.kamar+' kamar');
  return parts.length?parts.join(' · '):'Tidak ada transaksi';
};

/* [CASHBOOK_V4] CashbookTab — top-bar context (date+shift+staff), 3 buttons, per-shift blocks, footer recap, notes inside-shift + general */
function CashbookTab(){
  const today=new Date().toISOString().slice(0,10);
  const [date,setDate]=useState(today);
  const [shift,setShift]=useState(cbAutoShift());
  const [staff,setStaff]=useState(()=>{
    try{return localStorage.getItem('cb_last_staff')||'Puput'}catch(e){return 'Puput'}
  });
  const [entries,setEntries]=useState(null);
  const [notes,setNotes]=useState([]);
  const [summary,setSummary]=useState(null);
  const [modalMode,setModalMode]=useState(null); // 'income' | 'expense' | 'note' | null
  const [compactOpen,setCompactOpen]=useState(false); // [CASHBOOK_V7] tampilan ringkas
  const [waSending,setWaSending]=useState(null); // [CASHBOOK_V8] shiftKey while sending
  const [waSentKeys,setWaSentKeys]=useState({}); // [CASHBOOK_V8] session-scoped sent flags
  const [busy,setBusy]=useState(false);

  /* persist staff */
  useEffect(()=>{ try{localStorage.setItem('cb_last_staff',staff)}catch(e){} },[staff]);

  const load=()=>{
    apiGet('/api/cashbook?date='+date).then(setEntries).catch(()=>setEntries([]));
    apiGet('/api/cashbook/summary?date_from='+date+'&date_to='+date).then(setSummary).catch(()=>setSummary(null));
    apiGet('/api/cashbook/notes?date='+date).then(setNotes).catch(()=>setNotes([]));
  };
  useEffect(load,[date]);

  /* Submit batch — context comes from top-bar (date/shift/staff) */
  const submitBatch=async(lines)=>{
    const r=await fetch('/api/cashbook/batch',{
      method:'POST',credentials:'include',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify({entry_date:date, shift, staff_name:staff, lines})
    });
    if(!r.ok){const d=await r.json().catch(()=>({}));throw new Error(d.detail||'Submit failed')}
    setModalMode(null);load();
  };

  /* Submit note — content + includeShift (inherits date/staff from top-bar) */
  const submitNote=async(content, includeShift)=>{
    const r=await fetch('/api/cashbook/notes',{
      method:'POST',credentials:'include',
      headers:{'Content-Type':'application/json'},
      body:JSON.stringify({entry_date:date, shift:includeShift?shift:null, staff_name:staff, content})
    });
    if(!r.ok){const d=await r.json().catch(()=>({}));throw new Error(d.detail||'Submit failed')}
    setModalMode(null);load();
  };

  const deleteNote=async(id)=>{
    if(!confirm('Hapus catatan?'))return;
    setBusy(true);
    try{await fetch('/api/cashbook/notes/'+id,{method:'DELETE',credentials:'include'});load()}
    catch(e){alert('Gagal hapus catatan: '+(e.message||''))}
    finally{setBusy(false)}
  };

  /* [CASHBOOK_V8] reset sent-flags when date changes (each date stands alone) */
  useEffect(()=>{setWaSentKeys({})},[date]);

  /* [CASHBOOK_V8] send shift recap to WA group */
  const sendWA=async(shiftKey, shiftVal, staffName)=>{
    if(!confirm(`Kirim rekap ${shiftVal==='pagi'?'Pagi':'Malam'} - ${staffName} (${cbFormatDate(date)}) ke grup WA TRPH?`)) return;
    setWaSending(shiftKey);
    try{
      const r=await fetch('/api/cashbook/send-wa',{
        method:'POST',credentials:'include',
        headers:{'Content-Type':'application/json'},
        body:JSON.stringify({entry_date:date, shift:shiftVal, staff_name:staffName})
      });
      if(!r.ok){const d=await r.json().catch(()=>({}));throw new Error(d.detail||'Send failed')}
      setWaSentKeys(prev=>({...prev,[shiftKey]:true}));
      alert('Berhasil dikirim ke grup WA TRPH ✓');
    }catch(e){alert('Gagal kirim WA: '+(e.message||'unknown'))}
    finally{setWaSending(null)}
  };

  if(entries===null) return <LoadingState label="Memuat buku kas…"/>;

  const blocks=cbBlocksFromEntriesAndNotes(entries,notes);
  const generalNotes=notes.filter(n=>!n.shift);
  const fmtRp=(n)=>FMT.rp(n);
  const transferTotal=summary ? (summary.income_transfer_bca||0)+(summary.income_transfer_bsi||0) : 0;
  const ctxLabel=`${cbFormatDate(date)} · Shift ${shift==='pagi'?'Pagi':'Malam'} · ${staff}`;

  return <div className="fade-in zoom-110">
    {/* [CASHBOOK_V8] Top bar — pisah "Lihat tanggal" vs "Catat sebagai" */}
    <div className="cb-topbar-v8">
      <div className="cb-topbar-section">
        <div className="cb-topbar-label">Lihat tanggal</div>
        <input type="date" className="input" value={date} onChange={e=>setDate(e.target.value)}/>
      </div>
      <div className="cb-topbar-divider"></div>
      <div className="cb-topbar-section">
        <div className="cb-topbar-label">Catat sebagai</div>
        <div style={{display:'flex',gap:6}}>
          {/* [CASHBOOK_V11] Malam → auto-switch staff ke Tutur (tetep bisa diubah manual) */}
          <select className="input" style={{maxWidth:120}} value={shift} onChange={e=>{const v=e.target.value;setShift(v);if(v==='malam')setStaff('Tutur')}}>
            <option value="pagi">Pagi</option>
            <option value="malam">Malam</option>
          </select>
          <select className="input" style={{maxWidth:130}} value={staff} onChange={e=>setStaff(e.target.value)}>
            {CB_STAFF.map(s=><option key={s} value={s}>{s}</option>)}
          </select>
          <button className="btn btn-ghost btn-sm" onClick={()=>{setDate(today);const s=cbAutoShift();setShift(s);if(s==='malam')setStaff('Tutur')}} disabled={date===today&&shift===cbAutoShift()} title="Reset ke hari ini + auto shift">Reset</button>
        </div>
      </div>
    </div>

    {/* [CASHBOOK_V12] unified transaksi button — modal handles income+expense in one flow */}
    <div className="cb-action-buttons" style={{display:'flex',gap:8,flexWrap:'wrap',marginBottom:16}}>
      <button className="btn btn-primary" onClick={()=>setModalMode('tx')}>
        <Ic name="plus" size={14}/>Catat Transaksi
      </button>
      <button className="btn btn-ghost" onClick={()=>setModalMode('note')}>
        <Ic name="plus" size={14}/>Tambah Catatan
      </button>
      {/* [CASHBOOK_V7] tampilan ringkas */}
      <button className="btn btn-ghost" onClick={()=>setCompactOpen(true)} disabled={!entries || (entries.length===0 && notes.length===0)} title="Compact view buat screenshot ke WA">
        📋 Tampilan Ringkas
      </button>
    </div>

    {/* Day summary cards */}
    <div className="grid g4" style={{marginBottom:18,gridTemplateColumns:'repeat(auto-fit,minmax(150px,1fr))'}}>
      <div className="cb-metric">
        <div className="cb-metric-label">Cash masuk</div>
        <div className="cb-metric-value">{fmtRp(summary?.income_cash||0)}</div>
      </div>
      <div className="cb-metric">
        <div className="cb-metric-label">Transfer + EDC</div>
        <div className="cb-metric-value">{fmtRp(transferTotal+(summary?.income_edc||0))}</div>
      </div>
      <div className="cb-metric">
        <div className="cb-metric-label">Pengeluaran</div>
        <div className="cb-metric-value" style={{color:(summary?.outcome_total||0)>0?'var(--bad)':'var(--ink)'}}>{fmtRp(summary?.outcome_total||0)}</div>
      </div>
      <div className="cb-metric">
        <div className="cb-metric-label">Net · Kamar</div>
        <div className="cb-metric-value">{fmtRp(summary?.net||0)} <span className="text-xs muted" style={{fontWeight:400}}>· {summary?.room_count||0} kamar</span></div>
      </div>
    </div>

    {/* Per-shift blocks */}
    {blocks.length===0 && generalNotes.length===0 ?
      <div className="card" style={{padding:32,textAlign:'center'}}>
        <div style={{fontSize:13,color:'var(--ink-3)',marginBottom:10}}>Belum ada entry untuk tanggal ini.</div>
        <div className="row gap-8" style={{justifyContent:'center',flexWrap:'wrap'}}>
          <button className="btn btn-primary btn-sm" onClick={()=>setModalMode('tx')}>
            <Ic name="plus" size={12}/>Catat Transaksi
          </button>
          <button className="btn btn-ghost btn-sm" onClick={()=>setModalMode('note')}>
            <Ic name="plus" size={12}/>Tambah Catatan
          </button>
        </div>
      </div>
      :
      blocks.map(b=>{
        /* [CASHBOOK_V8] sort + separator + WA send key */
        const sorted=cbSortEntries(b.entries);
        const incomeCount=sorted.filter(e=>cbIsIncome(e.category)).length;
        const hasIncome=incomeCount>0;
        const hasExpense=sorted.length-incomeCount>0;
        const shiftKey=`${b.shift}_${b.staff_name}`;
        const isSending=waSending===shiftKey;
        const isSent=!!waSentKeys[shiftKey];
        const canSend=b.entries.length>0 || b.notes.length>0;
        return <div key={shiftKey} className="cb-shift-block">
        <div className="cb-shift-header">
          <div className="row gap-8" style={{alignItems:'center',flexWrap:'wrap'}}>
            <span className={`badge ${b.shift==='pagi'?'b-info':'b-accent'}`} style={{textTransform:'uppercase',fontSize:10}}>{b.shift==='pagi'?'Pagi':'Malam'}</span>
            <div className="serif" style={{fontSize:16}}>{b.staff_name}</div>
            <span className="text-xs muted">· {cbFormatDate(date)}</span>
          </div>
        </div>
        {/* [CASHBOOK_V7] body only when there are entries; footer always renders so orphan-note blocks show "Tidak ada transaksi" */}
        {sorted.length>0 && <div className="cb-shift-body">
          {sorted.map((e,idx)=><React.Fragment key={e.id}>
            {idx===incomeCount && hasIncome && hasExpense && <div className="cb-section-separator">─── Pengeluaran ───</div>}
            <CashbookEditRow entry={e} onUpdate={load} onDelete={load}/>
          </React.Fragment>)}
        </div>}
        <div className="cb-shift-footer">
          <span>{cbShiftFooter(b.entries)}</span>
          {/* [CASHBOOK_V9] kirim ke WA — filled WA-green, SVG icon, no emoji */}
          {canSend && (isSent ? (
            <span className="cb-wa-sent">
              <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" style={{flexShrink:0}}><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>
              Sudah dikirim
            </span>
          ) : (
            <button className="cb-wa-btn"
                    onClick={()=>sendWA(shiftKey,b.shift,b.staff_name)}
                    disabled={isSending || waSending!==null}
                    title="Kirim rekap ke grup WA TRPH">
              {isSending ? <>
                <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" className="cb-wa-spin" style={{flexShrink:0}}><path d="M12 4V2A10 10 0 002 12h2a8 8 0 018-8z"/></svg>
                Mengirim…
              </> : <>
                <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor" style={{flexShrink:0}}><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
                Kirim ke WA
              </>}
            </button>
          ))}
        </div>
        {b.notes.length>0 && <div className="cb-shift-notes">
          {b.notes.map(n=><div key={n.id} className="cb-note-row">
            <div className="cb-note-content">{n.content}</div>
            <button className="ghost-sm" title="Hapus catatan" onClick={()=>deleteNote(n.id)} disabled={busy}>×</button>
          </div>)}
        </div>}
      </div>;
      })
    }

    {/* General notes (no shift) */}
    {generalNotes.length>0 && <div style={{marginTop:18}}>
      <div className="cb-section-title">Catatan umum (tanpa shift) · {generalNotes.length}</div>
      <div className="card">
        {generalNotes.map(n=>{
          const meta=[n.staff_name||'—'];
          if(n.created_at) meta.push(new Date(n.created_at).toLocaleTimeString('id-ID',{hour:'2-digit',minute:'2-digit'}));
          return <div key={n.id} className="cb-note-row">
            <div className="cb-note-meta">
              <span>{meta.join(' · ')}</span>
              <button className="ico-btn cb-ico" title="Hapus catatan" onClick={()=>deleteNote(n.id)} disabled={busy}>
                <Ic name="trash" size={12}/>
              </button>
            </div>
            <div className="cb-note-content">{n.content}</div>
          </div>;
        })}
      </div>
    </div>}

    {/* Modals — context inherited from top-bar */}
    {(modalMode==='tx' || modalMode==='income' || modalMode==='expense') && <CashbookSimpleModal mode={modalMode==='tx'?undefined:modalMode} contextLabel={ctxLabel} onClose={()=>setModalMode(null)} onSubmit={submitBatch} onAddNote={()=>setModalMode('note')}/>}
    {modalMode==='note' && <CashbookNoteModalSimple
      contextLabel={`${cbFormatDate(date)} · ${staff}`}
      shiftLabel={shift==='pagi'?'Pagi':'Malam'}
      onClose={()=>setModalMode(null)}
      onSubmit={submitNote}/>}
    {/* [CASHBOOK_V7] tampilan ringkas */}
    {compactOpen && <CashbookCompactView
      date={date}
      blocks={blocks}
      generalNotes={generalNotes}
      summary={summary}
      onClose={()=>setCompactOpen(false)}/>}
  </div>;
}

/* [CASHBOOK_V4] CashbookEditRow — self-contained: own edit/delete state, view↔edit toggle */
function CashbookEditRow({entry, onUpdate, onDelete}){
  const [editing,setEditing]=useState(false);
  const [busy,setBusy]=useState(false);
  const [amt,setAmt]=useState(parseRp(String(entry.amount)));
  const [room,setRoom]=useState(entry.room_number||'');
  const [desc,setDesc]=useState(entry.description||'');
  const [cat,setCat]=useState(entry.category);
  const [pm,setPm]=useState(entry.payment_method);

  const startEdit=()=>{
    setAmt(parseRp(String(entry.amount)));
    setRoom(entry.room_number||'');
    setDesc(entry.description||'');
    setCat(entry.category);
    setPm(entry.payment_method);
    setEditing(true);
  };

  const save=async()=>{
    const a=Number(amt);
    if(!a||a<=0){alert('Nominal tidak valid');return}
    setBusy(true);
    try{
      const r=await fetch('/api/cashbook/'+entry.id,{
        method:'PATCH',credentials:'include',
        headers:{'Content-Type':'application/json'},
        body:JSON.stringify({category:cat,room_number:room||null,description:desc||null,amount:a,payment_method:pm})
      });
      if(!r.ok){const d=await r.json().catch(()=>({}));throw new Error(d.detail||'Update failed')}
      setEditing(false);if(onUpdate)onUpdate();
    }catch(e){alert('Gagal update: '+e.message)}
    finally{setBusy(false)}
  };

  const del=async()=>{
    if(!confirm('Hapus entry ini?'))return;
    setBusy(true);
    try{
      await fetch('/api/cashbook/'+entry.id,{method:'DELETE',credentials:'include'});
      if(onDelete)onDelete();
    }catch(e){alert('Gagal hapus: '+e.message)}
    finally{setBusy(false)}
  };

  if(editing){
    /* Default cb-line cb-edit grid: 96px 56px 1fr 110px 70px 60px. Override (drop Kamar slot) when not room_payment. */
    const editStyle = cat==='room_payment' ? undefined : {gridTemplateColumns:'96px 1fr 110px 70px 60px'};
    return <div className="cb-line cb-edit" style={editStyle}>
      <select className="input" style={{padding:'4px 6px',fontSize:11}} value={cat} onChange={e=>{const v=e.target.value;setCat(v);if(v!=='room_payment')setRoom('')}}>
        {CB_CATEGORIES.map(c=><option key={c.value} value={c.value}>{c.label}</option>)}
      </select>
      {cat==='room_payment' &&
        <input className="input" style={{padding:'4px 6px',fontSize:12}} placeholder="kmr" value={room} onChange={e=>setRoom(e.target.value)}/>}
      <input className="input" style={{padding:'4px 6px',fontSize:12}} placeholder="keterangan" value={desc} onChange={e=>setDesc(e.target.value)}/>
      <input className="input cb-amt" style={{padding:'4px 6px',fontSize:12}} type="text" inputMode="numeric" placeholder="Rp0" value={formatRp(amt)} onChange={e=>setAmt(parseRp(e.target.value))}/>
      <select className="input" style={{padding:'4px 6px',fontSize:11}} value={pm} onChange={e=>setPm(e.target.value)}>
        {CB_METHODS.map(m=><option key={m.value} value={m.value}>{m.label}</option>)}
      </select>
      <span className="row gap-8" style={{justifyContent:'flex-end'}}>
        <button className="ico-btn cb-ico" title="Simpan" onClick={save} disabled={busy}><Ic name="check" size={12}/></button>
        <button className="ico-btn cb-ico" title="Batal" onClick={()=>setEditing(false)} disabled={busy}><Ic name="close" size={12}/></button>
      </span>
    </div>;
  }

  const catMeta=CB_CAT_MAP[entry.category]||{label:entry.category,pill:'p-out'};
  const methMeta=CB_METHOD_MAP[entry.payment_method]||{label:entry.payment_method};
  return <div className="cb-line">
    <span className={`cb-pill ${catMeta.pill}`}>{catMeta.label}</span>
    <span className="mono text-xs">{entry.room_number||''}</span>
    <span style={{minWidth:0,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}} title={entry.description||''}>{entry.description||''}</span>
    {/* [CASHBOOK_V10] minus prefix + subtle red tint for expense */}
    <span className={`cb-amt ${!cbIsIncome(entry.category)?'cb-amt-expense':''}`}>{cbAmountDisplay(entry)}</span>
    <span className="text-xs muted">{methMeta.label}</span>
    <span className="row gap-8" style={{justifyContent:'flex-end'}}>
      <button className="ico-btn cb-ico" title="Edit" onClick={startEdit} disabled={busy}><Ic name="edit" size={12}/></button>
      <button className="ico-btn cb-ico" title="Hapus" onClick={del} disabled={busy}><Ic name="trash" size={12}/></button>
    </span>
  </div>;
}

/* [CASHBOOK_V12] CashbookSimpleModal — UNIFIED income+expense in one modal w/ per-line type toggle */
function CashbookSimpleModal({mode, contextLabel, onClose, onSubmit, onAddNote}){
  /* mode optional, defaults first line kind. Lines can mix freely after. */
  const initialKind = mode==='expense' ? 'expense' : 'income';
  const defaultCatFor = (kind)=> kind==='income' ? 'room_payment' : 'expense_supplies';
  const newLine=(kind=initialKind)=>({_kind:kind, category:defaultCatFor(kind), room_number:'', description:'', amount:'', payment_method:'cash'});
  const [lines,setLines]=useState([newLine()]);
  const [busy,setBusy]=useState(false);

  const addLine=(kind)=>setLines(ls=>{
    const k = kind || (ls.length ? ls[ls.length-1]._kind : 'income');
    return [...ls, newLine(k)];
  });
  const removeLine=(i)=>setLines(ls=>ls.length>1?ls.filter((_,idx)=>idx!==i):ls);
  const updLine=(i,k,v)=>setLines(ls=>ls.map((l,idx)=>idx===i?{...l,[k]:v}:l));
  const toggleKind=(i)=>setLines(ls=>ls.map((l,idx)=>{
    if(idx!==i) return l;
    const newKind = l._kind==='income' ? 'expense' : 'income';
    return {...l, _kind:newKind, category:defaultCatFor(newKind), room_number:''};
  }));

  /* Counts for footer summary */
  const incomeLines = lines.filter(l=>l._kind==='income' && l.amount!=='' && Number(l.amount)>0);
  const expenseLines = lines.filter(l=>l._kind==='expense' && l.amount!=='' && Number(l.amount)>0);
  const totalIn = incomeLines.reduce((s,l)=>s+Number(l.amount),0);
  const totalOut = expenseLines.reduce((s,l)=>s+Number(l.amount),0);

  const submit=async()=>{
    const valid=lines.filter(l=>l.amount!==''&&Number(l.amount)>0);
    if(valid.length===0){alert('Minimal 1 baris dengan nominal > 0');return}
    setBusy(true);
    try{
      await onSubmit(valid.map(l=>({
        category:l.category,
        room_number:(l.category==='room_payment') && (l.room_number||'').trim()?l.room_number.trim():null,
        description:(l.description||'').trim()||null,
        amount:Number(l.amount),
        payment_method:l.payment_method
      })));
    }catch(e){alert('Gagal simpan: '+(e.message||'unknown'))}
    finally{setBusy(false)}
  };

  /* Grid templates: with-Kamar vs without-Kamar (chosen per-row by category) */
  const gridColsWithRoom = '70px 130px 1fr 70px 130px 100px 32px';
  const gridColsNoRoom   = '70px 130px 1fr 130px 100px 32px';
  /* Header follows: kalau gak ada baris yang room_payment, sembunyiin kolom Kamar di header juga */
  const anyRoomLine = lines.some(l=>l.category==='room_payment');
  const gridCols = anyRoomLine ? gridColsWithRoom : gridColsNoRoom;

  return <div onClick={onClose} style={{position:'fixed',inset:0,background:'rgba(20,15,10,0.4)',zIndex:90,display:'flex',alignItems:'flex-start',justifyContent:'center',paddingTop:'8vh'}}>
    <div onClick={e=>e.stopPropagation()} className="fade-in cb-modal-content" style={{width:840,maxWidth:'94vw',maxHeight:'86vh',background:'var(--surface)',border:'1px solid var(--line)',borderRadius:14,boxShadow:'var(--shadow-lg)',display:'flex',flexDirection:'column'}}>
      <div className="card-h" style={{borderBottom:'1px solid var(--line)',flexDirection:'column',alignItems:'stretch',gap:0}}>
        <div className="between">
          <h3>Catat Transaksi</h3>
          <button className="ico-btn" style={{width:28,height:28}} onClick={onClose}><Ic name="close" size={14}/></button>
        </div>
        <div className="modal-context">Untuk: {contextLabel}</div>
      </div>
      <div style={{padding:'8px 12px',overflowY:'auto',flex:1}}>
        <div style={{display:'grid',gridTemplateColumns:gridCols,gap:8,padding:'6px 4px',fontSize:10,letterSpacing:'0.06em',textTransform:'uppercase',color:'var(--ink-4)',fontWeight:600}}>
          <div>Jenis</div>
          <div>Kategori</div>
          <div>Deskripsi</div>
          {anyRoomLine && <div>Kamar</div>}
          <div className="r">Nominal</div>
          <div>Metode</div>
          <div></div>
        </div>
        {lines.map((l,i)=>{
          const isIncome = l._kind==='income';
          const cats = isIncome ? CB_INCOME_CATEGORIES : CB_EXPENSE_CATEGORIES;
          const rowCols = (l.category==='room_payment') ? gridColsWithRoom : gridColsNoRoom;
          return <div key={i} style={{display:'grid',gridTemplateColumns:rowCols,gap:8,padding:'4px',alignItems:'center'}}>
            <select
              className="input"
              value={l._kind}
              onChange={e=>{
                const newKind=e.target.value;
                if(newKind===l._kind) return;
                setLines(ls=>ls.map((ln,idx)=>idx===i?{...ln,_kind:newKind,category:defaultCatFor(newKind),room_number:''}:ln));
              }}
              style={{
                fontSize:12, fontWeight:600,
                color:isIncome?'#16a34a':'#dc2626',
                borderColor:isIncome?'#16a34a':'#dc2626',
                background:isIncome?'rgba(22,163,74,0.06)':'rgba(220,38,38,0.06)'
              }}>
              <option value="income">Masuk</option>
              <option value="expense">Keluar</option>
            </select>
            <select className="input" value={l.category} onChange={e=>{
              const v=e.target.value;
              setLines(ls=>ls.map((ln,idx)=>idx===i?{...ln,category:v,room_number:v==='room_payment'?ln.room_number:''}:ln));
            }}>
              {cats.map(c=><option key={c.value} value={c.value}>{c.label}</option>)}
            </select>
            <input className="input" placeholder={isIncome?'keterangan opsional':'mis. 8 dus Sangua'} value={l.description} onChange={e=>updLine(i,'description',e.target.value)}/>
            {l.category==='room_payment' &&
              <input className="input mono" placeholder="—" value={l.room_number} onChange={e=>updLine(i,'room_number',e.target.value)}/>
            }
            <input className="input cb-amt" type="text" inputMode="numeric" placeholder="Rp0"
                   value={formatRp(l.amount)}
                   onChange={e=>updLine(i,'amount',parseRp(e.target.value))}/>
            <select className="input" value={l.payment_method} onChange={e=>updLine(i,'payment_method',e.target.value)}>
              {CB_METHODS.map(m=><option key={m.value} value={m.value}>{m.label}</option>)}
            </select>
            <button className="ico-btn cb-ico" title="Hapus baris" onClick={()=>removeLine(i)} disabled={lines.length<=1}><Ic name="close" size={12}/></button>
          </div>;
        })}
        <div className="row gap-8" style={{marginTop:8}}>
          <button className="btn btn-ghost btn-sm" style={{flex:1,justifyContent:'center'}} onClick={()=>addLine('income')}>
            <Ic name="plus" size={12}/>+ Pemasukan
          </button>
          <button className="btn btn-ghost btn-sm" style={{flex:1,justifyContent:'center'}} onClick={()=>addLine('expense')}>
            <Ic name="plus" size={12}/>+ Pengeluaran
          </button>
        </div>
      </div>
      <div style={{padding:'12px 16px',borderTop:'1px solid var(--line)',background:'var(--surface-2)',display:'flex',flexDirection:'column',gap:10}}>
        {(incomeLines.length>0 || expenseLines.length>0) &&
          <div className="row gap-8" style={{justifyContent:'space-between',fontSize:12}}>
            <span className="muted">
              {incomeLines.length>0 && <span style={{color:'#16a34a'}}>{incomeLines.length} masuk · {fmtRp(totalIn)}</span>}
              {incomeLines.length>0 && expenseLines.length>0 && <span className="muted"> · </span>}
              {expenseLines.length>0 && <span style={{color:'#dc2626'}}>{expenseLines.length} keluar · {fmtRp(totalOut)}</span>}
            </span>
            <span className="mono" style={{fontWeight:600}}>Net: {fmtRp(totalIn-totalOut)}</span>
          </div>
        }
        <div style={{display:'flex',gap:10,justifyContent:'space-between',alignItems:'center'}}>
          {onAddNote ?
            <button className="btn btn-ghost btn-sm" onClick={()=>{
              const hasData = lines.some(l=>l.amount!==''&&Number(l.amount)>0);
              if(hasData && !confirm('Ada transaksi belum disimpan. Lanjut tambah catatan? (data transaksi akan hilang)')) return;
              onAddNote();
            }} disabled={busy}>
              <Ic name="plus" size={12}/>Tambah Catatan
            </button>
            : <span/>
          }
          <div style={{display:'flex',gap:10}}>
            <button className="btn btn-ghost" onClick={onClose} disabled={busy}>Batal</button>
            <button className="btn btn-primary" onClick={submit} disabled={busy}>
              {busy?'Menyimpan…':`Simpan ${lines.filter(l=>l.amount!==''&&Number(l.amount)>0).length} transaksi`}
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>;
}

/* [CASHBOOK_V4] CashbookNoteModalSimple — content + checkbox includeShift */
/* [CASHBOOK_V7] CashbookCompactView — clean B&W layout for screenshot → WA grup */
function CashbookCompactView({date, blocks, generalNotes, summary, onClose}){
  const transferTotal=summary?(summary.income_transfer_bca||0)+(summary.income_transfer_bsi||0)+(summary.income_edc||0):0;
  return <div className="cb-compact-overlay" onClick={onClose}>
    <div className="cb-compact-frame" onClick={e=>e.stopPropagation()}>
      <div className="cb-compact-header">
        <div style={{fontWeight:600,fontSize:14}}>The Raden Patah Heritage</div>
        <div style={{fontSize:11,color:'#666',marginTop:2}}>Buku Kas Harian — {cbFormatDate(date)}</div>
      </div>
      {summary && <div className="cb-compact-summary">
        <div className="cb-compact-row"><span>Cash masuk</span><span><strong>{FMT.rp(summary.income_cash||0)}</strong></span></div>
        <div className="cb-compact-row"><span>Transfer + EDC</span><span><strong>{FMT.rp(transferTotal)}</strong></span></div>
        <div className="cb-compact-row"><span>Pengeluaran</span><span><strong>{FMT.rp(summary.outcome_total||0)}</strong></span></div>
        <div className="cb-compact-row" style={{borderTop:'0.5px solid #ddd',paddingTop:6,marginTop:4}}>
          <span><strong>Net</strong></span>
          <span><strong>{FMT.rp(summary.net||0)}</strong></span>
        </div>
        <div className="cb-compact-row"><span>Kamar terjual</span><span><strong>{summary.room_count||0}</strong></span></div>
      </div>}
      {blocks.map(b=>{
        /* [CASHBOOK_V8] sort + separator (text-only style for compact view) */
        const sorted=cbSortEntries(b.entries);
        const incomeCount=sorted.filter(e=>cbIsIncome(e.category)).length;
        const hasIncome=incomeCount>0;
        const hasExpense=sorted.length-incomeCount>0;
        return <div key={`${b.shift}_${b.staff_name}`} className="cb-compact-shift">
        <div className="cb-compact-shift-title">{b.shift==='pagi'?'Pagi':'Malam'} — {b.staff_name}</div>
        {sorted.length===0 ? <div className="cb-compact-empty">Tidak ada transaksi</div> :
        <>
          {sorted.map((e,idx)=><React.Fragment key={e.id}>
            {idx===incomeCount && hasIncome && hasExpense && <div className="cb-compact-line" style={{justifyContent:'center',color:'#888',fontStyle:'italic',fontSize:10}}>─── Pengeluaran ───</div>}
            <div className="cb-compact-line">
              <span>{CB_CATEGORY_LABEL[e.category]||e.category}{e.room_number?` ${e.room_number}`:''}{e.description?` · ${e.description}`:''}</span>
              {/* [CASHBOOK_V10] minus prefix; compact view stays B&W for screenshot */}
              <span>{cbAmountDisplay(e)} <span style={{color:'#888'}}>{CB_METHOD_LABEL[e.payment_method]||e.payment_method}</span></span>
            </div>
          </React.Fragment>)}
          <div className="cb-compact-shift-footer">{cbShiftFooter(b.entries)}</div>
        </>}
        {b.notes.length>0 && <div className="cb-compact-notes">
          {b.notes.map(n=><div key={n.id} className="cb-compact-note">📝 {n.content}</div>)}
        </div>}
      </div>;
      })}
      {generalNotes && generalNotes.length>0 && <div className="cb-compact-shift">
        <div className="cb-compact-shift-title">Catatan umum</div>
        {generalNotes.map(n=><div key={n.id} className="cb-compact-note">📝 {n.staff_name}: {n.content}</div>)}
      </div>}
      <div className="cb-compact-footer">ai.theradenpatah.com</div>
      <button onClick={onClose} className="cb-compact-close">Tutup</button>
    </div>
  </div>;
}

function CashbookNoteModalSimple({contextLabel, shiftLabel, onClose, onSubmit}){
  const [content,setContent]=useState('');
  const [includeShift,setIncludeShift]=useState(true);
  const [busy,setBusy]=useState(false);

  const submit=async()=>{
    const c=content.trim();
    if(!c){alert('Catatan tidak boleh kosong');return}
    setBusy(true);
    try{
      await onSubmit(c, includeShift);
    }catch(e){alert('Gagal simpan: '+(e.message||'unknown'))}
    finally{setBusy(false)}
  };

  return <div onClick={onClose} style={{position:'fixed',inset:0,background:'rgba(20,15,10,0.4)',zIndex:90,display:'flex',alignItems:'flex-start',justifyContent:'center',paddingTop:'10vh'}}>
    <div onClick={e=>e.stopPropagation()} className="fade-in cb-modal-content" style={{width:520,maxWidth:'94vw',background:'var(--surface)',border:'1px solid var(--line)',borderRadius:14,boxShadow:'var(--shadow-lg)',display:'flex',flexDirection:'column'}}>
      <div className="card-h" style={{borderBottom:'1px solid var(--line)',flexDirection:'column',alignItems:'stretch',gap:0}}>
        <div className="between">
          <h3>Tambah Catatan</h3>
          <button className="ico-btn" style={{width:28,height:28}} onClick={onClose}><Ic name="close" size={14}/></button>
        </div>
        <div className="modal-context">Untuk: {contextLabel}{includeShift?` · Shift ${shiftLabel}`:' · Tanpa shift'}</div>
      </div>
      <div style={{padding:'14px 16px'}}>
        <label style={{display:'flex',alignItems:'center',gap:8,fontSize:13,color:'var(--ink-2)',marginBottom:12,cursor:'pointer'}}>
          <input type="checkbox" checked={includeShift} onChange={e=>setIncludeShift(e.target.checked)} style={{accentColor:'var(--ink)',width:16,height:16}}/>
          Catat untuk shift {shiftLabel}
        </label>
        <div className="label">Catatan</div>
        <textarea className="input" rows={5} placeholder="Tulis catatan di sini…"
                  value={content} onChange={e=>setContent(e.target.value)}
                  style={{resize:'vertical',minHeight:100,fontFamily:'inherit'}}/>
      </div>
      <div style={{padding:'12px 16px',borderTop:'1px solid var(--line)',display:'flex',gap:10,justifyContent:'flex-end',background:'var(--surface-2)'}}>
        <button className="btn btn-ghost" onClick={onClose} disabled={busy}>Batal</button>
        <button className="btn btn-primary" onClick={submit} disabled={busy||!content.trim()}>
          {busy?'Menyimpan…':'Simpan catatan'}
        </button>
      </div>
    </div>
  </div>;
}

/* [CASHBOOK_V4] Laporan Buku Kas — Detail harian (default) + Breakdown analitik (accordion) */
function CashbookReportTab(){
  const today=new Date().toISOString().slice(0,10);
  const monthStart=today.slice(0,8)+'01';
  const [from,setFrom]=useState(monthStart);
  const [to,setTo]=useState(today);
  const [report,setReport]=useState(null);
  const [busy,setBusy]=useState(false);
  const [err,setErr]=useState(null);
  const [showAnalytics,setShowAnalytics]=useState(false);
  /* [CASHBOOK_V6] export + audit state */
  const [exporting,setExporting]=useState(null); // 'excel' | 'pdf' | null
  const [showAudit,setShowAudit]=useState(false);
  const [auditRows,setAuditRows]=useState(null);

  const load=async()=>{
    setBusy(true);setErr(null);
    try{
      const r=await apiGet('/api/cashbook/report?date_from='+from+'&date_to='+to);
      setReport(r);
    }catch(e){setReport(null);setErr('Gagal memuat laporan')}
    finally{setBusy(false)}
  };
  useEffect(()=>{load()},[from,to]);
  /* [CASHBOOK_V6] invalidate audit cache when range changes */
  useEffect(()=>{setAuditRows(null)},[from,to]);
  /* [CASHBOOK_V6] lazy-load audit when accordion opened */
  useEffect(()=>{
    if(showAudit && auditRows===null){
      apiGet('/api/cashbook/audit?date_from='+from+'&date_to='+to)
        .then(setAuditRows).catch(()=>setAuditRows([]));
    }
  },[showAudit,from,to,auditRows]);

  /* [CASHBOOK_V6] trigger export → download via blob URL */
  const exportFile=async(format)=>{
    setExporting(format);
    try{
      const r=await fetch(`/api/cashbook/export/${format}?date_from=${from}&date_to=${to}`,{
        method:'POST',credentials:'include'
      });
      if(!r.ok){
        const d=await r.json().catch(()=>({}));
        throw new Error(d.detail||'Export failed');
      }
      const data=await r.json();
      if(data.url && data.filename){
        const a=document.createElement('a');
        a.href=data.url;
        a.download=data.filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      }
    }catch(e){alert('Gagal export: '+(e.message||'unknown'))}
    finally{setExporting(null)}
  };

  /* Group entries+notes by entry_date → per (shift, staff_name) block */
  const dailyGroups=useMemo(()=>{
    if(!report) return [];
    const byDate={};
    (report.entries||[]).forEach(e=>{
      const d=e.entry_date;
      if(!byDate[d]) byDate[d]={date:d, blocks:{}};
      const key=`${e.shift}__${e.staff_name}`;
      if(!byDate[d].blocks[key]) byDate[d].blocks[key]={shift:e.shift,staff_name:e.staff_name,entries:[],notes:[],firstAt:e.created_at};
      byDate[d].blocks[key].entries.push(e);
    });
    (report.notes||[]).forEach(n=>{
      const d=n.entry_date;
      if(!byDate[d]) byDate[d]={date:d, blocks:{}, generalNotes:[]};
      if(n.shift && n.staff_name){
        const key=`${n.shift}__${n.staff_name}`;
        if(byDate[d].blocks[key]) byDate[d].blocks[key].notes.push(n);
        else {
          /* note's shift+staff has no entries — synthesize an empty block so the note still surfaces */
          byDate[d].blocks[key]={shift:n.shift,staff_name:n.staff_name,entries:[],notes:[n],firstAt:n.created_at};
        }
      } else {
        if(!byDate[d].generalNotes) byDate[d].generalNotes=[];
        byDate[d].generalNotes.push(n);
      }
    });
    return Object.values(byDate)
      .sort((a,b)=>a.date.localeCompare(b.date))
      .map(d=>({
        ...d,
        blocks:Object.values(d.blocks).sort((a,b)=>{
          if(a.shift!==b.shift) return a.shift==='pagi'?-1:1;
          return new Date(a.firstAt||0)-new Date(b.firstAt||0);
        })
      }));
  },[report]);

  const fmtRp=(n)=>FMT.rp(n);
  const sum=report?.summary;

  return <div className="fade-in zoom-110">
    <div className="between" style={{marginBottom:18,flexWrap:'wrap',gap:12}}>
      <div>
        <div className="serif" style={{fontSize:22,letterSpacing:'-0.01em'}}>Laporan Buku Kas</div>
        <div className="text-xs muted" style={{marginTop:4}}>Rekapan periode {cbFormatDate(from)} s/d {cbFormatDate(to)}</div>
      </div>
      <div className="row gap-8" style={{alignItems:'center',flexWrap:'wrap'}}>
        <input type="date" className="input" style={{maxWidth:170}} value={from} onChange={e=>setFrom(e.target.value)}/>
        <span className="muted">—</span>
        <input type="date" className="input" style={{maxWidth:170}} value={to} onChange={e=>setTo(e.target.value)}/>
        {/* [CASHBOOK_V6] export buttons */}
        <button className="btn btn-primary btn-sm" onClick={()=>exportFile('excel')} disabled={exporting!==null||busy||!report}>
          <Ic name="download" size={12}/>{exporting==='excel'?'Generating…':'Export Excel'}
        </button>
        <button className="btn btn-ghost btn-sm" onClick={()=>exportFile('pdf')} disabled={exporting!==null||busy||!report}>
          <Ic name="download" size={12}/>{exporting==='pdf'?'Generating…':'Export PDF'}
        </button>
      </div>
    </div>

    {busy && !report ? <LoadingState label="Memuat laporan…"/> :
     err ? <EmptyState msg={err}/> :
     !report ? null :
     <>
      <div className="metric-grid-4">
        <div className="cb-metric"><div className="cb-metric-label">Income</div><div className="cb-metric-value">{fmtRp(sum.income_total)}</div></div>
        <div className="cb-metric"><div className="cb-metric-label">Pengeluaran</div><div className="cb-metric-value" style={{color:sum.outcome_total>0?'var(--bad)':'var(--ink)'}}>{fmtRp(sum.outcome_total)}</div></div>
        <div className="cb-metric"><div className="cb-metric-label">Net</div><div className="cb-metric-value" style={{color:sum.net<0?'var(--bad)':'var(--ink)'}}>{fmtRp(sum.net)}</div></div>
        <div className="cb-metric"><div className="cb-metric-label">Kamar terjual</div><div className="cb-metric-value">{sum.room_count}</div></div>
      </div>

      {/* Detail harian — default expanded */}
      <div className="cb-section-title" style={{marginTop:18}}>Detail harian</div>
      {dailyGroups.length===0 && <div className="card" style={{padding:24,textAlign:'center',color:'var(--ink-4)',fontSize:13}}>Belum ada data di periode ini.</div>}
      {dailyGroups.map(d=>{
        /* [CASHBOOK_V5] flatten entries across all shifts in the day for total footer */
        const dayEntries=d.blocks.flatMap(b=>b.entries);
        return <div key={d.date} className="cb-day-block">
        <h4>{cbFormatDate(d.date)}</h4>
        {d.blocks.map(b=>{
          /* [CASHBOOK_V8] sort + separator in report Detail harian */
          const sorted=cbSortEntries(b.entries);
          const incomeCount=sorted.filter(e=>cbIsIncome(e.category)).length;
          const hasIncome=incomeCount>0;
          const hasExpense=sorted.length-incomeCount>0;
          return <div key={`${d.date}_${b.shift}_${b.staff_name}`} className="cb-shift-block">
          <div className="cb-shift-header">
            <div className="row gap-8" style={{alignItems:'center',flexWrap:'wrap'}}>
              <span className={`badge ${b.shift==='pagi'?'b-info':'b-accent'}`} style={{textTransform:'uppercase',fontSize:10}}>{b.shift==='pagi'?'Pagi':'Malam'}</span>
              <div className="serif" style={{fontSize:15}}>{b.staff_name}</div>
            </div>
          </div>
          {sorted.length>0 && <div className="cb-shift-body">
            {sorted.map((e,idx)=>{
              const catMeta=CB_CAT_MAP[e.category]||{label:e.category,pill:'p-out'};
              const methMeta=CB_METHOD_MAP[e.payment_method]||{label:e.payment_method};
              return <React.Fragment key={e.id}>
                {idx===incomeCount && hasIncome && hasExpense && <div className="cb-section-separator">─── Pengeluaran ───</div>}
                <div className="cb-line-readonly">
                  <span className={`cb-pill ${catMeta.pill}`}>{catMeta.label}</span>
                  <span className="mono text-xs">{e.room_number||''}</span>
                  <span className="muted" style={{minWidth:0,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}} title={e.description||''}>{e.description||'—'}</span>
                  {/* [CASHBOOK_V10] minus prefix + subtle red tint for expense */}
                  <span className={`cb-amt ${!cbIsIncome(e.category)?'cb-amt-expense':''}`}>{cbAmountDisplay(e)}</span>
                  <span className="text-xs muted">{methMeta.label}</span>
                </div>
              </React.Fragment>;
            })}
          </div>}
          {b.entries.length>0 && <div className="cb-shift-footer">{cbShiftFooter(b.entries)}</div>}
          {b.notes.length>0 && <div className="cb-shift-notes">
            {b.notes.map(n=><div key={n.id} className="cb-note-row">
              <div className="cb-note-content">{n.content}</div>
            </div>)}
          </div>}
        </div>;
        })}
        {(d.generalNotes||[]).length>0 && <div className="card" style={{marginTop:8,padding:'10px 14px',background:'#FFFCEF'}}>
          <div className="text-xs muted" style={{marginBottom:6,letterSpacing:'0.04em',textTransform:'uppercase',fontWeight:600}}>Catatan umum</div>
          {d.generalNotes.map(n=><div key={n.id} style={{fontSize:13,padding:'4px 0',whiteSpace:'pre-wrap',color:'var(--ink-2)'}}>
            <span className="text-xs muted">{n.staff_name} · </span>{n.content}
          </div>)}
        </div>}
        {/* [CASHBOOK_V5] day total footer */}
        {dayEntries.length>0 && <div className="cb-day-footer">
          <span className="cb-day-footer-label">Total {cbFormatDate(d.date)}:</span>
          <span className="cb-day-footer-recap">{cbShiftFooter(dayEntries)}</span>
        </div>}
      </div>;
      })}

      {/* Breakdown analitik — collapsible accordion, collapsed by default */}
      <div style={{marginTop:24,borderTop:'1px solid var(--line)',paddingTop:14}}>
        <button className="cb-accordion-btn" onClick={()=>setShowAnalytics(v=>!v)}>
          <span style={{fontSize:12,width:14,display:'inline-block'}}>{showAnalytics?'▾':'▸'}</span>
          Breakdown analitik
        </button>
        {showAnalytics && <div style={{marginTop:14}}>
          <div className="cb-section-title">Per tanggal</div>
          <div className="cb-report-table-wrapper"><table className="cb-report-table">
            <thead><tr><th>Tanggal</th><th className="r">Income</th><th className="r">Outcome</th><th className="r">Net</th><th className="r">Kamar</th></tr></thead>
            <tbody>
              {report.daily.length===0 ? <tr><td colSpan="5" style={{textAlign:'center',color:'var(--ink-4)',padding:18}}>—</td></tr> :
                report.daily.map(d=><tr key={d.date}>
                  <td className="mono text-xs">{d.date}</td>
                  <td className="r">{fmtRp(d.income)}</td>
                  <td className="r">{fmtRp(d.outcome)}</td>
                  <td className="r" style={{color:d.net<0?'var(--bad)':'var(--ink)'}}>{fmtRp(d.net)}</td>
                  <td className="r">{d.room_count}</td>
                </tr>)}
            </tbody>
          </table></div>

          <div className="cb-section-title">Per kategori</div>
          <div className="cb-report-table-wrapper"><table className="cb-report-table">
            <thead><tr><th>Kategori</th><th className="r">Total</th><th className="r">Jumlah entri</th><th className="r">%</th></tr></thead>
            <tbody>
              {report.by_category.length===0 ? <tr><td colSpan="4" style={{textAlign:'center',color:'var(--ink-4)',padding:18}}>—</td></tr> :
                report.by_category.map(c=><tr key={c.category}>
                  <td>{CB_CATEGORY_LABEL[c.category]||c.category}</td>
                  <td className="r">{fmtRp(c.total)}</td>
                  <td className="r">{c.count}</td>
                  <td className="r">{(c.pct||0).toFixed(1)}%</td>
                </tr>)}
            </tbody>
          </table></div>

          <div className="cb-section-title">Per staff</div>
          <div className="cb-report-table-wrapper"><table className="cb-report-table">
            <thead><tr><th>Staff</th><th className="r">Income</th><th className="r">Outcome</th><th className="r">Net</th><th className="r">Jumlah shift</th></tr></thead>
            <tbody>
              {report.by_staff.length===0 ? <tr><td colSpan="5" style={{textAlign:'center',color:'var(--ink-4)',padding:18}}>—</td></tr> :
                report.by_staff.map(s=><tr key={s.staff}>
                  <td>{s.staff}</td>
                  <td className="r">{fmtRp(s.income)}</td>
                  <td className="r">{fmtRp(s.outcome)}</td>
                  <td className="r" style={{color:s.net<0?'var(--bad)':'var(--ink)'}}>{fmtRp(s.net)}</td>
                  <td className="r">{s.shift_count}</td>
                </tr>)}
            </tbody>
          </table></div>

          <div className="cb-section-title">Per metode bayar</div>
          <div className="cb-report-table-wrapper"><table className="cb-report-table">
            <thead><tr><th>Metode</th><th className="r">Total</th><th className="r">Jumlah entri</th></tr></thead>
            <tbody>
              {report.by_method.length===0 ? <tr><td colSpan="3" style={{textAlign:'center',color:'var(--ink-4)',padding:18}}>—</td></tr> :
                report.by_method.map(m=><tr key={m.method}>
                  <td>{CB_METHOD_LABEL[m.method]||m.method}</td>
                  <td className="r">{fmtRp(m.total)}</td>
                  <td className="r">{m.count}</td>
                </tr>)}
            </tbody>
          </table></div>
        </div>}
      </div>

      {/* [CASHBOOK_V6] Audit log — collapsible accordion, collapsed by default */}
      <div style={{marginTop:14,borderTop:'1px solid var(--line)',paddingTop:14}}>
        <button className="cb-accordion-btn" onClick={()=>setShowAudit(v=>!v)}>
          <span style={{fontSize:12,width:14,display:'inline-block'}}>{showAudit?'▾':'▸'}</span>
          Audit log (perubahan & penghapusan)
        </button>
        {showAudit && <div style={{marginTop:14}}>
          {auditRows===null && <LoadingState label="Memuat audit log…"/>}
          {auditRows && auditRows.length===0 && <div className="card" style={{padding:18,textAlign:'center',color:'var(--ink-4)',fontSize:13}}>Belum ada perubahan di periode ini.</div>}
          {auditRows && auditRows.length>0 && <div className="cb-report-table-wrapper"><table className="cb-report-table">
            <thead><tr><th>Waktu</th><th>User</th><th>Aksi</th><th>Tipe</th><th>ID</th><th>Detail</th></tr></thead>
            <tbody>
              {auditRows.map(a=><tr key={a.id}>
                <td className="text-xs mono">{new Date(a.changed_at).toLocaleString('id-ID',{year:'numeric',month:'short',day:'2-digit',hour:'2-digit',minute:'2-digit'})}</td>
                <td>{a.changed_by}</td>
                <td><span style={{color:CB_ACTION_COLOR[a.action]||'var(--ink)',fontWeight:600}}>{CB_ACTION_LABEL[a.action]||a.action}</span></td>
                <td>{CB_ENTITY_LABEL[a.entity_type]||a.entity_type}</td>
                <td className="mono text-xs">{a.entity_id}</td>
                <td style={{fontSize:12,color:'var(--ink-2)'}}>{cbAuditDiff(a)}</td>
              </tr>)}
            </tbody>
          </table></div>}
        </div>}
      </div>
     </>
    }
  </div>;
}
/* ---------------- /CASHBOOK ---------------- */

/* [NALA_CASHBOOK_V1] expose ke script utama */
window.CashbookTab = CashbookTab;
window.CashbookReportTab = CashbookReportTab;
})();
