const { useState, useEffect, useCallback } = React;

const CLASS = { 1: "Asset", 2: "Liability", 3: "Equity", 4: "Income", 5: "Expense" };
const DOC = { 0: "Draft", 1: "Posted", 2: "Voided" };
const JSTATUS = { 0: "Draft", 1: "Posted", 2: "Reversed" };
const PSTATUS = { 0: "Open", 1: "Closed", 2: "Locked" };
const PBADGE = { 0: "ok", 1: "draft", 2: "bad" };
const SSTTYPE = { SstSales: "Sales Tax", SstService: "Service Tax", ZeroRated: "Zero-rated", Exempt: "Exempt", None: "None" };
const money = n => "RM " + Number(n || 0).toLocaleString("en-MY", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const today = () => new Date().toISOString().slice(0, 10);

function useLoad(fn, deps) {
  const [data, setData] = useState(null);
  const [err, setErr] = useState(null);
  const reload = useCallback(() => { fn().then(setData).catch(e => setErr(e.message)); }, deps || []);
  useEffect(() => { reload(); }, [reload]);
  return [data, err, reload];
}

const byClass = (accs, ...cls) => (accs || []).filter(a => cls.includes(a.class));
const banks = accs => (accs || []).filter(a => a.type === 1);

/* ─── shared document line editor (invoices / bills) ─── */
function DocLines({ lines, setLines, accounts, taxes, kind, properties }) {
  const opts = kind === "revenue" ? byClass(accounts, 4) : byClass(accounts, 5, 1);
  const props = properties || [];
  const up = (i, k, v) => { const n = lines.slice(); n[i] = { ...n[i], [k]: v }; setLines(n); };
  const total = lines.reduce((s, l) => s + (Number(l.quantity) * Number(l.unitPrice) || 0), 0);
  return (
    <div>
      <table className="full">
        <thead><tr><th>Account</th><th>Description</th><th className="num">Qty</th><th className="num">Unit price</th>{taxes && <th>Tax</th>}{props.length > 0 && <th>Property</th>}<th></th></tr></thead>
        <tbody>{lines.map((l, i) => <tr key={i}>
          <td><select value={l.accountId} onChange={e => up(i, "accountId", e.target.value)}><option value="">—</option>{opts.map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></td>
          <td><input value={l.description} onChange={e => up(i, "description", e.target.value)} /></td>
          <td className="num"><input type="number" style={{ width: 66 }} value={l.quantity} onChange={e => up(i, "quantity", e.target.value)} /></td>
          <td className="num"><input type="number" style={{ width: 92 }} value={l.unitPrice} onChange={e => up(i, "unitPrice", e.target.value)} /></td>
          {taxes && <td><select value={l.taxCodeId} onChange={e => up(i, "taxCodeId", e.target.value)}><option value="">None</option>{taxes.map(t => <option key={t.id} value={t.id}>{t.code}</option>)}</select></td>}
          {props.length > 0 && <td><select value={l.dimensionId || ""} onChange={e => up(i, "dimensionId", e.target.value)}><option value="">—</option>{props.map(d => <option key={d.id} value={d.id}>{d.code} {d.name}</option>)}</select></td>}
          <td>{lines.length > 1 && <button className="btn sm ghost" onClick={() => setLines(lines.filter((_, j) => j !== i))}>✕</button>}</td>
        </tr>)}</tbody>
      </table>
      <div className="row" style={{ marginTop: 8, justifyContent: "space-between" }}>
        <button className="btn sm ghost" onClick={() => setLines([...lines, blankLine()])}>+ Add line</button>
        <span className="muted">Subtotal (excl. tax): <b>{money(total)}</b></span>
      </div>
    </div>
  );
}
const blankLine = () => ({ accountId: "", description: "", quantity: 1, unitPrice: 0, taxCodeId: "", dimensionId: "" });
const toLines = ls => ls.filter(l => l.accountId).map(l => ({ accountId: l.accountId, description: l.description, quantity: Number(l.quantity), unitPrice: Number(l.unitPrice), taxCodeId: l.taxCodeId || null, dimensionId: l.dimensionId || null }));

function Login() {
  return (
    <div className="login"><div className="card">
      <div className="lock"><img src="/img/onebloc-accounting.png" alt="OneBloc Accounting" width="42" height="42" /><span className="brand"><span className="one">One</span><span className="bloc">Bloc</span><span className="mod">Account</span></span></div>
      <p className="muted" style={{ margin: "16px 0 22px" }}>Double-entry accounting for your business.<br />Sign in with your OneBloc Connect account.</p>
      <button className="btn" onClick={() => API.login()}>Sign in with OneBloc Connect →</button>
    </div></div>
  );
}

function Dashboard() {
  const [accs] = useLoad(() => API.get("/accounts"));
  const [custs] = useLoad(() => API.get("/customers"));
  const [invs] = useLoad(() => API.get("/invoices"));
  const [tb] = useLoad(() => API.get("/reports/trial-balance"));
  const kpi = (k, v) => <div className="kpi"><div className="k">{k}</div><div className="v">{v}</div></div>;
  const outstanding = (invs || []).filter(i => i.status === 1).reduce((s, i) => s + (i.balanceDue || 0), 0);
  return (
    <div>
      <div className="head"><div><h1>Dashboard</h1><div className="sub">Your books at a glance</div></div></div>
      <div className="cards">
        {kpi("Accounts", accs ? accs.length : "—")}
        {kpi("Customers", custs ? custs.length : "—")}
        {kpi("Invoices", invs ? invs.length : "—")}
        {kpi("Receivable", money(outstanding))}
      </div>
      <div className="card"><h3>Ledger health</h3>
        {tb ? <p>Trial balance is {tb.isBalanced ? <span className="badge ok">In balance</span> : <span className="badge bad">Out of balance</span>} — debits {money(tb.totalDebit)}, credits {money(tb.totalCredit)}.</p> : <p className="muted">Loading…</p>}
      </div>
    </div>
  );
}

function Accounts() {
  const [accs, err, reload] = useLoad(() => API.get("/accounts"));
  const [e2, setE2] = useState(null);
  const [na, setNa] = useState({ code: "", name: "", cls: 1, type: 0 });
  const create = async () => {
    setE2(null);
    try {
      await API.post("/accounts", { code: na.code, name: na.name, class: Number(na.cls), type: Number(na.type) });
      setNa({ code: "", name: "", cls: 1, type: 0 }); reload();
    } catch (er) { setE2(er.message); }
  };
  const toggle = async a => { try { await API.post(`/accounts/${a.id}/active?active=${!a.isActive}`); reload(); } catch (er) { setE2(er.message); } };
  const accType = a => a.type === 1 ? "Bank / Cash" : (a.isControl ? "Control" : "General");
  return (
    <div>
      <div className="head">
        <div><h1>Chart of Accounts</h1><div className="sub">Add accounts &amp; bank accounts · export</div></div>
        <button className="btn" onClick={() => { window.location.href = "/api/accounts/export"; }}>⬇ Export to Excel</button>
      </div>
      {(err || e2) && <div className="err">{err || e2}</div>}
      <div className="card"><h3>New account</h3>
        <div className="row" style={{ alignItems: "end" }}>
          <div><label>Code</label><input style={{ width: 96 }} placeholder="1330" value={na.code} onChange={e => setNa({ ...na, code: e.target.value })} /></div>
          <div style={{ flex: 2 }}><label>Name</label><input placeholder="Maybank Current A/C" value={na.name} onChange={e => setNa({ ...na, name: e.target.value })} /></div>
          <div><label>Class</label><select value={na.cls} onChange={e => setNa({ ...na, cls: e.target.value })}>
            <option value="1">Asset</option><option value="2">Liability</option><option value="3">Equity</option><option value="4">Income</option><option value="5">Expense</option></select></div>
          <div><label>Type</label><select value={na.type} onChange={e => setNa({ ...na, type: e.target.value })}>
            <option value="0">General</option><option value="1">Bank / Cash</option></select></div>
          <div><button className="btn sm" onClick={create}>Add</button></div>
        </div>
        <p className="muted" style={{ marginTop: 8 }}>A <b>bank account</b> = Asset + type <b>Bank / Cash</b>. It then appears in Receipts, Payments, Vouchers &amp; Reconciliation.</p>
      </div>
      <div className="card"><table className="full">
        <thead><tr><th>Code</th><th>Name</th><th>Class</th><th>Type</th><th>Normal</th><th></th></tr></thead>
        <tbody>{(accs || []).map(a => <tr key={a.id} style={a.isActive ? null : { opacity: .45 }}>
          <td className="pill">{a.code}</td><td>{a.name}</td><td>{CLASS[a.class]}</td>
          <td className="muted">{accType(a)}{a.isControl && <span className="badge posted" style={{ marginLeft: 6 }}>control</span>}</td>
          <td className="muted">{a.isDebitNormal ? "Debit" : "Credit"}</td>
          <td>{!a.isControl && <button className="btn sm ghost" onClick={() => toggle(a)}>{a.isActive ? "Deactivate" : "Activate"}</button>}</td></tr>)}</tbody>
      </table></div>
    </div>
  );
}

function Sales() {
  const [custs, , reloadC] = useLoad(() => API.get("/customers"));
  const [invs, , reloadI] = useLoad(() => API.get("/invoices"));
  const [accs] = useLoad(() => API.get("/accounts"));
  const [taxes] = useLoad(() => API.get("/tax-codes"));
  const [dims] = useLoad(() => API.get("/dimensions"));
  const properties = (dims || []).filter(d => d.type === 2);
  const [err, setErr] = useState(null);
  const [nc, setNc] = useState({ code: "", name: "" });
  const [inv, setInv] = useState({ customerId: "", date: today() });
  const [lines, setLines] = useState([blankLine()]);
  const [rc, setRc] = useState({ customerId: "", bankAccountId: "", amount: "", invoiceId: "" });

  const addCustomer = async () => {
    if (!nc.code.trim() || !nc.name.trim()) { OBNotify.error("Customer code and name are both required."); return; }
    try { await API.post("/customers", nc); setNc({ code: "", name: "" }); reloadC(); OBNotify.success("Customer " + nc.name + " added."); } catch (e) { setErr(e.message); }
  };
  const createInvoice = async post => {
    setErr(null);
    if (!inv.customerId) { OBNotify.error("Select a customer for the invoice."); return; }
    if (!lines.some(l => l.accountId && Number(l.unitPrice) > 0)) { OBNotify.error("Add at least one line with an account and amount."); return; }
    try {
      const created = await API.post("/invoices", { customerId: inv.customerId, date: inv.date, lines: toLines(lines) });
      if (post) await API.post(`/invoices/${created.id}/post`);
      setLines([blankLine()]); reloadI();
      OBNotify.success(post ? ("Invoice " + created.invoiceNo + " posted to the ledger.") : ("Invoice " + created.invoiceNo + " saved as draft."));
    } catch (e) { setErr(e.message); }
  };
  const postInvoice = async id => { try { const r = await API.post(`/invoices/${id}/post`); reloadI(); OBNotify.success("Invoice " + (r.invoiceNo || "") + " posted."); } catch (e) { setErr(e.message); } };
  const openInv = (invs || []).filter(i => i.status === 1 && i.balanceDue > 0 && i.customerId === rc.customerId);
  const createReceipt = async () => {
    setErr(null);
    if (!rc.customerId) { OBNotify.error("Select the customer."); return; }
    if (!rc.bankAccountId) { OBNotify.error("Select the bank account."); return; }
    if (!(Number(rc.amount) > 0)) { OBNotify.error("Enter a receipt amount greater than zero."); return; }
    try {
      const alloc = rc.invoiceId ? [{ invoiceId: rc.invoiceId, amount: Number(rc.amount) }] : [];
      const created = await API.post("/receipts", { customerId: rc.customerId, date: today(), bankAccountId: rc.bankAccountId, amount: Number(rc.amount), method: 1, allocations: alloc });
      await API.post(`/receipts/${created.id}/post`);
      setRc({ customerId: "", bankAccountId: "", amount: "", invoiceId: "" }); reloadI();
      OBNotify.success("Receipt " + (created.receiptNo || "") + " for " + money(Number(rc.amount)) + " recorded.");
    } catch (e) { setErr(e.message); }
  };

  return (
    <div>
      <div className="head"><div><h1>Sales</h1><div className="sub">Customers · invoices · receipts (Accounts Receivable)</div></div></div>
      {err && <div className="err">{err}</div>}
      <div className="card"><h3>New invoice</h3>
        <div className="row" style={{ marginBottom: 10 }}>
          <div><label>Customer</label><select value={inv.customerId} onChange={e => setInv({ ...inv, customerId: e.target.value })}><option value="">— select —</option>{(custs || []).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}</select></div>
          <div><label>Date</label><input type="date" value={inv.date} onChange={e => setInv({ ...inv, date: e.target.value })} /></div>
        </div>
        <DocLines lines={lines} setLines={setLines} accounts={accs} taxes={taxes} kind="revenue" properties={properties} />
        <div className="row" style={{ marginTop: 10 }}><button className="btn" onClick={() => createInvoice(true)}>Create &amp; Post</button><button className="btn ghost" onClick={() => createInvoice(false)}>Save draft</button></div>
      </div>
      <div className="card"><h3>Invoices</h3><table className="full">
        <thead><tr><th>No.</th><th>Date</th><th>Customer</th><th className="num">Total</th><th className="num">Balance</th><th>Status</th><th></th></tr></thead>
        <tbody>{(invs || []).map(i => <tr key={i.id}><td className="pill">{i.invoiceNo}</td><td>{i.date}</td><td>{i.customer ? i.customer.name : ""}</td>
          <td className="num">{money(i.total)}</td><td className="num">{money(i.balanceDue)}</td>
          <td><span className={"badge " + (i.status === 1 ? "posted" : "draft")}>{DOC[i.status]}</span></td>
          <td>{i.status === 0 && <button className="btn sm" onClick={() => postInvoice(i.id)}>Post</button>}</td></tr>)}</tbody>
      </table></div>
      <div className="card"><h3>Receive payment</h3><div className="row">
        <div><label>Customer</label><select value={rc.customerId} onChange={e => setRc({ ...rc, customerId: e.target.value, invoiceId: "" })}><option value="">—</option>{(custs || []).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}</select></div>
        <div><label>Bank</label><select value={rc.bankAccountId} onChange={e => setRc({ ...rc, bankAccountId: e.target.value })}><option value="">—</option>{banks(accs).map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
        <div><label>Invoice</label><select value={rc.invoiceId} onChange={e => { const iv = openInv.find(x => x.id === e.target.value); setRc({ ...rc, invoiceId: e.target.value, amount: iv ? iv.balanceDue : rc.amount }); }}><option value="">(on account)</option>{openInv.map(iv => <option key={iv.id} value={iv.id}>{iv.invoiceNo} — {money(iv.balanceDue)}</option>)}</select></div>
        <div><label>Amount</label><input type="number" value={rc.amount} onChange={e => setRc({ ...rc, amount: e.target.value })} /></div>
        <div style={{ alignSelf: "end" }}><button className="btn sm" onClick={createReceipt}>Receive</button></div>
      </div></div>
      <div className="card"><h3>Add customer</h3><div className="row">
        <input placeholder="Code" value={nc.code} onChange={e => setNc({ ...nc, code: e.target.value })} />
        <input placeholder="Name" value={nc.name} onChange={e => setNc({ ...nc, name: e.target.value })} />
        <button className="btn sm" onClick={addCustomer}>Add</button></div></div>
    </div>
  );
}

function Purchases() {
  const [sups, , reloadS] = useLoad(() => API.get("/suppliers"));
  const [bills, , reloadB] = useLoad(() => API.get("/bills"));
  const [accs] = useLoad(() => API.get("/accounts"));
  const [taxes] = useLoad(() => API.get("/tax-codes"));
  const [dims] = useLoad(() => API.get("/dimensions"));
  const properties = (dims || []).filter(d => d.type === 2);
  const [err, setErr] = useState(null);
  const [ns, setNs] = useState({ code: "", name: "" });
  const [bill, setBill] = useState({ supplierId: "", date: today() });
  const [lines, setLines] = useState([blankLine()]);
  const [pm, setPm] = useState({ supplierId: "", bankAccountId: "", amount: "", billId: "" });

  const addSupplier = async () => {
    if (!ns.code.trim() || !ns.name.trim()) { OBNotify.error("Supplier code and name are both required."); return; }
    try { await API.post("/suppliers", ns); setNs({ code: "", name: "" }); reloadS(); OBNotify.success("Supplier " + ns.name + " added."); } catch (e) { setErr(e.message); }
  };
  const createBill = async post => {
    setErr(null);
    if (!bill.supplierId) { OBNotify.error("Select a supplier for the bill."); return; }
    if (!lines.some(l => l.accountId && Number(l.unitPrice) > 0)) { OBNotify.error("Add at least one line with an account and amount."); return; }
    try { const created = await API.post("/bills", { supplierId: bill.supplierId, date: bill.date, lines: toLines(lines) }); if (post) await API.post(`/bills/${created.id}/post`); setLines([blankLine()]); reloadB(); OBNotify.success(post ? ("Bill " + created.billNo + " posted.") : ("Bill " + created.billNo + " saved as draft.")); } catch (e) { setErr(e.message); }
  };
  const postBill = async id => { try { const r = await API.post(`/bills/${id}/post`); reloadB(); OBNotify.success("Bill " + (r.billNo || "") + " posted."); } catch (e) { setErr(e.message); } };
  const openBills = (bills || []).filter(b => b.status === 1 && b.balanceDue > 0 && b.supplierId === pm.supplierId);
  const pay = async () => {
    setErr(null);
    if (!pm.supplierId) { OBNotify.error("Select the supplier."); return; }
    if (!pm.bankAccountId) { OBNotify.error("Select the bank account."); return; }
    if (!(Number(pm.amount) > 0)) { OBNotify.error("Enter a payment amount greater than zero."); return; }
    try { const alloc = pm.billId ? [{ billId: pm.billId, amount: Number(pm.amount) }] : []; const created = await API.post("/supplier-payments", { supplierId: pm.supplierId, date: today(), bankAccountId: pm.bankAccountId, amount: Number(pm.amount), method: 1, allocations: alloc }); await API.post(`/supplier-payments/${created.id}/post`); setPm({ supplierId: "", bankAccountId: "", amount: "", billId: "" }); reloadB(); OBNotify.success("Payment " + (created.paymentNo || "") + " of " + money(Number(pm.amount)) + " recorded."); } catch (e) { setErr(e.message); }
  };

  return (
    <div>
      <div className="head"><div><h1>Purchases</h1><div className="sub">Suppliers · bills · payments (Accounts Payable)</div></div></div>
      {err && <div className="err">{err}</div>}
      <div className="card"><h3>New bill</h3>
        <div className="row" style={{ marginBottom: 10 }}>
          <div><label>Supplier</label><select value={bill.supplierId} onChange={e => setBill({ ...bill, supplierId: e.target.value })}><option value="">— select —</option>{(sups || []).map(s => <option key={s.id} value={s.id}>{s.name}</option>)}</select></div>
          <div><label>Date</label><input type="date" value={bill.date} onChange={e => setBill({ ...bill, date: e.target.value })} /></div>
        </div>
        <DocLines lines={lines} setLines={setLines} accounts={accs} taxes={taxes} kind="expense" properties={properties} />
        <div className="row" style={{ marginTop: 10 }}><button className="btn" onClick={() => createBill(true)}>Create &amp; Post</button><button className="btn ghost" onClick={() => createBill(false)}>Save draft</button></div>
      </div>
      <div className="card"><h3>Bills</h3><table className="full">
        <thead><tr><th>No.</th><th>Date</th><th>Supplier</th><th className="num">Total</th><th className="num">Balance</th><th>Status</th><th></th></tr></thead>
        <tbody>{(bills || []).map(b => <tr key={b.id}><td className="pill">{b.billNo}</td><td>{b.date}</td><td>{b.supplier ? b.supplier.name : ""}</td>
          <td className="num">{money(b.total)}</td><td className="num">{money(b.balanceDue)}</td>
          <td><span className={"badge " + (b.status === 1 ? "posted" : "draft")}>{DOC[b.status]}</span></td>
          <td>{b.status === 0 && <button className="btn sm" onClick={() => postBill(b.id)}>Post</button>}</td></tr>)}</tbody>
      </table></div>
      <div className="card"><h3>Pay supplier</h3><div className="row">
        <div><label>Supplier</label><select value={pm.supplierId} onChange={e => setPm({ ...pm, supplierId: e.target.value, billId: "" })}><option value="">—</option>{(sups || []).map(s => <option key={s.id} value={s.id}>{s.name}</option>)}</select></div>
        <div><label>Bank</label><select value={pm.bankAccountId} onChange={e => setPm({ ...pm, bankAccountId: e.target.value })}><option value="">—</option>{banks(accs).map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
        <div><label>Bill</label><select value={pm.billId} onChange={e => { const b = openBills.find(x => x.id === e.target.value); setPm({ ...pm, billId: e.target.value, amount: b ? b.balanceDue : pm.amount }); }}><option value="">(on account)</option>{openBills.map(b => <option key={b.id} value={b.id}>{b.billNo} — {money(b.balanceDue)}</option>)}</select></div>
        <div><label>Amount</label><input type="number" value={pm.amount} onChange={e => setPm({ ...pm, amount: e.target.value })} /></div>
        <div style={{ alignSelf: "end" }}><button className="btn sm" onClick={pay}>Pay</button></div>
      </div></div>
      <div className="card"><h3>Add supplier</h3><div className="row">
        <input placeholder="Code" value={ns.code} onChange={e => setNs({ ...ns, code: e.target.value })} />
        <input placeholder="Name" value={ns.name} onChange={e => setNs({ ...ns, name: e.target.value })} />
        <button className="btn sm" onClick={addSupplier}>Add</button></div></div>
    </div>
  );
}

function Journals() {
  const [accs] = useLoad(() => API.get("/accounts"));
  const [dims] = useLoad(() => API.get("/dimensions"));
  const [js, , reload] = useLoad(() => API.get("/journals"));
  const [err, setErr] = useState(null);
  const [date, setDate] = useState(today());
  const [desc, setDesc] = useState("");
  const jrow = () => ({ accountId: "", debit: 0, credit: 0, dimensionId: "" });
  const [rows, setRows] = useState([jrow(), jrow()]);
  const properties = (dims || []).filter(d => d.type === 2);
  const up = (i, k, v) => { const n = rows.slice(); n[i] = { ...n[i], [k]: v }; setRows(n); };
  const td = rows.reduce((s, r) => s + Number(r.debit || 0), 0), tc = rows.reduce((s, r) => s + Number(r.credit || 0), 0);
  const postable = (accs || []).filter(a => !a.isControl);
  const post = async () => {
    setErr(null);
    if (Math.round(td * 100) !== Math.round(tc * 100)) { OBNotify.error("The journal is not balanced — debits " + money(td) + " must equal credits " + money(tc) + "."); return; }
    if (td === 0) { OBNotify.error("Enter at least one debit and one credit."); return; }
    try {
      const je = await API.post("/journals", { date, description: desc, lines: rows.filter(r => r.accountId).map(r => ({ accountId: r.accountId, debit: Number(r.debit), credit: Number(r.credit), dimensionId: r.dimensionId || null })) });
      setRows([jrow(), jrow()]); setDesc(""); reload();
      OBNotify.success("Journal " + (je.entryNo || "") + " posted.");
    } catch (e) { setErr(e.message); }
  };
  const reverse = async id => {
    if (!(await OBNotify.confirm({ title: "Reverse this entry?", message: "This posts a mirror journal that undoes it. The original stays on the record, untouched.", okText: "Reverse", danger: true }))) return;
    try { const r = await API.post(`/journals/${id}/reverse`, { date: today(), reason: "Reversed from UI" }); reload(); OBNotify.success("Entry reversed" + (r.entryNo ? " (" + r.entryNo + ")" : "") + "."); } catch (e) { setErr(e.message); }
  };
  return (
    <div>
      <div className="head"><div><h1>Journals</h1><div className="sub">Manual general-ledger entries</div></div></div>
      {err && <div className="err">{err}</div>}
      <div className="card"><h3>New journal entry</h3>
        <div className="row" style={{ marginBottom: 10 }}>
          <div><label>Date</label><input type="date" value={date} onChange={e => setDate(e.target.value)} /></div>
          <div style={{ flex: 2 }}><label>Description</label><input value={desc} onChange={e => setDesc(e.target.value)} /></div>
        </div>
        <table className="full"><thead><tr><th>Account</th><th className="num">Debit</th><th className="num">Credit</th><th>Property</th><th></th></tr></thead>
          <tbody>{rows.map((r, i) => <tr key={i}>
            <td><select value={r.accountId} onChange={e => up(i, "accountId", e.target.value)}><option value="">—</option>{postable.map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></td>
            <td className="num"><input type="number" style={{ width: 110 }} value={r.debit} onChange={e => up(i, "debit", e.target.value)} /></td>
            <td className="num"><input type="number" style={{ width: 110 }} value={r.credit} onChange={e => up(i, "credit", e.target.value)} /></td>
            <td><select value={r.dimensionId} onChange={e => up(i, "dimensionId", e.target.value)}><option value="">—</option>{properties.map(d => <option key={d.id} value={d.id}>{d.code} {d.name}</option>)}</select></td>
            <td>{rows.length > 2 && <button className="btn sm ghost" onClick={() => setRows(rows.filter((_, j) => j !== i))}>✕</button>}</td></tr>)}
            <tr className="tot"><td>{td === tc ? <span className="badge ok">balanced</span> : <span className="badge bad">off by {money(Math.abs(td - tc))}</span>}</td><td className="num">{money(td)}</td><td className="num">{money(tc)}</td><td></td><td></td></tr>
          </tbody></table>
        <div className="row" style={{ marginTop: 10 }}><button className="btn sm ghost" onClick={() => setRows([...rows, jrow()])}>+ Add line</button><button className="btn" onClick={post}>Post entry</button></div>
      </div>
      <div className="card"><h3>Recent entries</h3><table className="full">
        <thead><tr><th>No.</th><th>Date</th><th>Description</th><th className="num">Debit</th><th>Status</th><th></th></tr></thead>
        <tbody>{(js || []).map(j => <tr key={j.id}><td className="pill">{j.entryNo}</td><td>{j.date}</td><td>{j.description}</td>
          <td className="num">{money(j.totalDebit)}</td><td><span className={"badge " + (j.status === 1 ? "posted" : "draft")}>{JSTATUS[j.status]}</span></td>
          <td>{j.status === 1 && <button className="btn sm ghost" onClick={() => reverse(j.id)}>Reverse</button>}</td></tr>)}</tbody>
      </table></div>
    </div>
  );
}

function Banking() {
  const [accs] = useLoad(() => API.get("/accounts"));
  const [custs] = useLoad(() => API.get("/customers"));
  const [sups] = useLoad(() => API.get("/suppliers"));
  const [err, setErr] = useState(null);
  const [pv, setPv] = useState({ bankAccountId: "", payeeName: "", accountId: "", amount: "" });
  const [rv, setRv] = useState({ bankAccountId: "", payerName: "", accountId: "", amount: "" });
  const [ct, setCt] = useState({ customerId: "", supplierId: "", amount: "" });
  const run = async (fn) => { setErr(null); try { await fn(); } catch (e) { setErr(e.message); } };
  const postPV = () => run(async () => { if (!pv.bankAccountId || !pv.accountId || !(Number(pv.amount) > 0)) { OBNotify.error("Bank, account and a positive amount are required."); return; } const c = await API.post("/payment-vouchers", { date: today(), bankAccountId: pv.bankAccountId, payeeName: pv.payeeName, method: 1, lines: [{ accountId: pv.accountId, description: pv.payeeName, amount: Number(pv.amount) }] }); await API.post(`/payment-vouchers/${c.id}/post`); setPv({ bankAccountId: "", payeeName: "", accountId: "", amount: "" }); OBNotify.success("Payment voucher " + (c.voucherNo || "") + " posted."); });
  const postRV = () => run(async () => { if (!rv.bankAccountId || !rv.accountId || !(Number(rv.amount) > 0)) { OBNotify.error("Bank, account and a positive amount are required."); return; } const c = await API.post("/receipt-vouchers", { date: today(), bankAccountId: rv.bankAccountId, payerName: rv.payerName, method: 1, lines: [{ accountId: rv.accountId, description: rv.payerName, amount: Number(rv.amount) }] }); await API.post(`/receipt-vouchers/${c.id}/post`); setRv({ bankAccountId: "", payerName: "", accountId: "", amount: "" }); OBNotify.success("Receipt voucher " + (c.voucherNo || "") + " posted."); });
  const postCT = () => run(async () => { if (!ct.customerId || !ct.supplierId || !(Number(ct.amount) > 0)) { OBNotify.error("Customer, supplier and a positive amount are required."); return; } const c = await API.post("/contras", { customerId: ct.customerId, supplierId: ct.supplierId, date: today(), amount: Number(ct.amount) }); await API.post(`/contras/${c.id}/post`); setCt({ customerId: "", supplierId: "", amount: "" }); OBNotify.success("Contra " + (c.contraNo || "") + " posted."); });
  return (
    <div>
      <div className="head"><div><h1>Banking</h1><div className="sub">Vouchers &amp; contra</div></div></div>
      {err && <div className="err">{err}</div>}
      <div className="card"><h3>Payment voucher (cash out)</h3><div className="row">
        <div><label>Bank</label><select value={pv.bankAccountId} onChange={e => setPv({ ...pv, bankAccountId: e.target.value })}><option value="">—</option>{banks(accs).map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
        <div><label>Payee</label><input value={pv.payeeName} onChange={e => setPv({ ...pv, payeeName: e.target.value })} /></div>
        <div><label>Expense account</label><select value={pv.accountId} onChange={e => setPv({ ...pv, accountId: e.target.value })}><option value="">—</option>{byClass(accs, 5, 1).map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
        <div><label>Amount</label><input type="number" value={pv.amount} onChange={e => setPv({ ...pv, amount: e.target.value })} /></div>
        <div style={{ alignSelf: "end" }}><button className="btn sm" onClick={postPV}>Post</button></div>
      </div></div>
      <div className="card"><h3>Receipt voucher (cash in)</h3><div className="row">
        <div><label>Bank</label><select value={rv.bankAccountId} onChange={e => setRv({ ...rv, bankAccountId: e.target.value })}><option value="">—</option>{banks(accs).map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
        <div><label>Payer</label><input value={rv.payerName} onChange={e => setRv({ ...rv, payerName: e.target.value })} /></div>
        <div><label>Income account</label><select value={rv.accountId} onChange={e => setRv({ ...rv, accountId: e.target.value })}><option value="">—</option>{byClass(accs, 4).map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
        <div><label>Amount</label><input type="number" value={rv.amount} onChange={e => setRv({ ...rv, amount: e.target.value })} /></div>
        <div style={{ alignSelf: "end" }}><button className="btn sm" onClick={postRV}>Post</button></div>
      </div></div>
      <div className="card"><h3>Contra (offset AR vs AP)</h3><div className="row">
        <div><label>Customer</label><select value={ct.customerId} onChange={e => setCt({ ...ct, customerId: e.target.value })}><option value="">—</option>{(custs || []).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}</select></div>
        <div><label>Supplier</label><select value={ct.supplierId} onChange={e => setCt({ ...ct, supplierId: e.target.value })}><option value="">—</option>{(sups || []).map(s => <option key={s.id} value={s.id}>{s.name}</option>)}</select></div>
        <div><label>Amount</label><input type="number" value={ct.amount} onChange={e => setCt({ ...ct, amount: e.target.value })} /></div>
        <div style={{ alignSelf: "end" }}><button className="btn sm" onClick={postCT}>Post</button></div>
      </div></div>
    </div>
  );
}

function FixedAssets() {
  const [accs] = useLoad(() => API.get("/accounts"));
  const [assets, , reload] = useLoad(() => API.get("/assets"));
  const [err, setErr] = useState(null);
  const [msg, setMsg] = useState(null);
  const [depDate, setDepDate] = useState(today());
  const [f, setF] = useState({ code: "", name: "", category: "", acquisitionDate: today(), cost: "", residualValue: 0, method: 0, usefulLifeMonths: 60, annualRatePct: 0, assetAccountId: "", accumulatedDepreciationAccountId: "", depreciationExpenseAccountId: "" });
  const sel = (k, label, opts) => <div><label>{label}</label><select value={f[k]} onChange={e => setF({ ...f, [k]: e.target.value })}><option value="">—</option>{opts.map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>;
  const create = async () => { setErr(null); try { await API.post("/assets", { ...f, cost: Number(f.cost), residualValue: Number(f.residualValue), method: Number(f.method), usefulLifeMonths: Number(f.usefulLifeMonths), annualRatePct: Number(f.annualRatePct) }); reload(); } catch (e) { setErr(e.message); } };
  const depreciate = async () => { setErr(null); setMsg(null); try { const r = await API.post(`/assets/depreciate?asOf=${depDate}`, {}); setMsg(`Depreciated ${r.assetsDepreciated} asset(s), ${money(r.totalDepreciation)} posted.`); reload(); } catch (e) { setErr(e.message); } };
  return (
    <div>
      <div className="head"><div><h1>Fixed Assets</h1><div className="sub">Register &amp; depreciation</div></div>
        <div className="row" style={{ alignItems: "end" }}><div><label>Run as of</label><input type="date" value={depDate} onChange={e => setDepDate(e.target.value)} /></div><button className="btn" onClick={depreciate}>Run depreciation</button></div>
      </div>
      {err && <div className="err">{err}</div>}
      {msg && <div className="card" style={{ background: "#dcfce7" }}>{msg}</div>}
      <div className="card"><h3>Register asset</h3><div className="form">
        <div className="row"><div><label>Code</label><input value={f.code} onChange={e => setF({ ...f, code: e.target.value })} /></div><div style={{ flex: 2 }}><label>Name</label><input value={f.name} onChange={e => setF({ ...f, name: e.target.value })} /></div><div><label>Acquired</label><input type="date" value={f.acquisitionDate} onChange={e => setF({ ...f, acquisitionDate: e.target.value })} /></div></div>
        <div className="row"><div><label>Cost</label><input type="number" value={f.cost} onChange={e => setF({ ...f, cost: e.target.value })} /></div><div><label>Residual</label><input type="number" value={f.residualValue} onChange={e => setF({ ...f, residualValue: e.target.value })} /></div>
          <div><label>Method</label><select value={f.method} onChange={e => setF({ ...f, method: e.target.value })}><option value="0">Straight-line</option><option value="1">Reducing balance</option></select></div>
          {Number(f.method) === 0 ? <div><label>Life (months)</label><input type="number" value={f.usefulLifeMonths} onChange={e => setF({ ...f, usefulLifeMonths: e.target.value })} /></div> : <div><label>Rate %/yr</label><input type="number" value={f.annualRatePct} onChange={e => setF({ ...f, annualRatePct: e.target.value })} /></div>}</div>
        <div className="row">{sel("assetAccountId", "Asset account", byClass(accs, 1))}{sel("accumulatedDepreciationAccountId", "Accum. depreciation", byClass(accs, 1))}{sel("depreciationExpenseAccountId", "Depreciation expense", byClass(accs, 5))}</div>
        <div><button className="btn" onClick={create}>Add asset</button></div>
      </div></div>
      <div className="card"><h3>Assets</h3><table className="full">
        <thead><tr><th>Code</th><th>Name</th><th className="num">Cost</th><th className="num">Accum. depr.</th><th className="num">Net book value</th></tr></thead>
        <tbody>{(assets || []).map(a => <tr key={a.id}><td className="pill">{a.code}</td><td>{a.name}</td><td className="num">{money(a.cost)}</td><td className="num">{money(a.accumulatedDepreciation)}</td><td className="num">{money(a.netBookValue)}</td></tr>)}</tbody>
      </table></div>
    </div>
  );
}

function Reports() {
  const [tab, setTab] = useState("tb");
  const [tb] = useLoad(() => API.get("/reports/trial-balance"));
  const [pl] = useLoad(() => API.get("/reports/profit-loss"));
  const [bs] = useLoad(() => API.get("/reports/balance-sheet"));
  const [cf] = useLoad(() => API.get("/reports/cash-flow"));
  const [accs] = useLoad(() => API.get("/accounts"));
  const [cbAcct, setCbAcct] = useState("");
  const [cb, setCb] = useState(null);
  const banks = (accs || []).filter(a => a.type === 1);
  useEffect(() => { if (!cbAcct && banks.length) setCbAcct(banks[0].id); }, [accs]);
  useEffect(() => { if (cbAcct) API.get("/reports/cash-book/" + cbAcct).then(setCb).catch(() => setCb(null)); }, [cbAcct]);
  const [sstFrom, setSstFrom] = useState("");
  const [sstTo, setSstTo] = useState("");
  const [sst, setSst] = useState(null);
  const loadSst = () => { const q = new URLSearchParams(); if (sstFrom) q.set("from", sstFrom); if (sstTo) q.set("to", sstTo); API.get("/reports/sst-return" + (q.toString() ? "?" + q.toString() : "")).then(setSst).catch(() => setSst(null)); };
  useEffect(() => { if (tab === "sst" && !sst) loadSst(); }, [tab]);
  const [settleBank, setSettleBank] = useState("");
  const [settleDate, setSettleDate] = useState(today());
  const [settleMsg, setSettleMsg] = useState(null);
  const settleSst = async () => {
    if (!settleBank || !sst) return;
    setSettleMsg(null);
    try { const je = await API.post("/reports/sst-settle", { from: sst.from, to: sst.to, bankAccountId: settleBank, payDate: settleDate }); setSettleMsg("Posted " + (je.entryNo || "settlement") + "."); }
    catch (e) { setSettleMsg(e.message); }
  };
  const T = ({ id, label }) => <button className={"tab" + (tab === id ? " active" : "")} onClick={() => setTab(id)}>{label}</button>;
  return (
    <div>
      <div className="head"><div><h1>Reports</h1><div className="sub">As of {today()}</div></div></div>
      <div className="tabs"><T id="tb" label="Trial Balance" /><T id="pl" label="Profit &amp; Loss" /><T id="bs" label="Balance Sheet" /><T id="cf" label="Cash Flow" /><T id="cb" label="Cash Book" /><T id="sst" label="SST-02" /></div>
      {tab === "tb" && tb && <div className="card"><table className="full"><thead><tr><th>Code</th><th>Account</th><th className="num">Debit</th><th className="num">Credit</th></tr></thead>
        <tbody>{tb.rows.map((r, i) => <tr key={i}><td className="pill">{r.code}</td><td>{r.name}</td><td className="num">{r.debit ? money(r.debit) : ""}</td><td className="num cr">{r.credit ? money(r.credit) : ""}</td></tr>)}
          <tr className="tot"><td></td><td>Total</td><td className="num">{money(tb.totalDebit)}</td><td className="num cr">{money(tb.totalCredit)}</td></tr></tbody></table></div>}
      {tab === "pl" && pl && <div className="card"><h3>Income</h3><table className="full"><tbody>{pl.income.map((r, i) => <tr key={i}><td>{r.code} {r.name}</td><td className="num">{money(r.amount)}</td></tr>)}<tr className="tot"><td>Total income</td><td className="num">{money(pl.totalIncome)}</td></tr></tbody></table>
        <h3 style={{ marginTop: 18 }}>Expenses</h3><table className="full"><tbody>{pl.expenses.map((r, i) => <tr key={i}><td>{r.code} {r.name}</td><td className="num">{money(r.amount)}</td></tr>)}<tr className="tot"><td>Total expenses</td><td className="num">{money(pl.totalExpenses)}</td></tr><tr className="tot"><td>Net profit</td><td className="num">{money(pl.netProfit)}</td></tr></tbody></table></div>}
      {tab === "bs" && bs && <div className="card">{[["Assets", bs.assets, bs.totalAssets], ["Liabilities", bs.liabilities, bs.totalLiabilities], ["Equity", bs.equity, bs.totalEquity]].map(([t, rows, tot]) =>
        <div key={t}><h3 style={{ marginTop: 8 }}>{t}</h3><table className="full"><tbody>{rows.map((r, i) => <tr key={i}><td>{r.code} {r.name}</td><td className="num">{money(r.amount)}</td></tr>)}<tr className="tot"><td>Total {t.toLowerCase()}</td><td className="num">{money(tot)}</td></tr></tbody></table></div>)}
        <p style={{ marginTop: 14 }}>{bs.isBalanced ? <span className="badge ok">Balanced</span> : <span className="badge bad">Out of balance</span>}</p></div>}
      {tab === "cf" && cf && <div className="card"><p>Opening cash: <b>{money(cf.openingCash)}</b></p>
        {cf.sections.map((s, i) => <div key={i}><h3 style={{ marginTop: 10 }}>{s.title}</h3><table className="full"><tbody>{s.rows.map((r, j) => <tr key={j}><td>{r.code} {r.name}</td><td className="num">{money(r.amount)}</td></tr>)}<tr className="tot"><td>Net {s.title.toLowerCase()}</td><td className="num">{money(s.total)}</td></tr></tbody></table></div>)}
        <table className="full" style={{ marginTop: 12 }}><tbody><tr className="tot"><td>Net cash flow</td><td className="num">{money(cf.netCashFlow)}</td></tr><tr className="tot"><td>Closing cash</td><td className="num">{money(cf.closingCash)}</td></tr></tbody></table>
        <p style={{ marginTop: 8 }}>{cf.reconciles ? <span className="badge ok">Reconciles</span> : <span className="badge bad">Does not reconcile</span>}</p></div>}
      {tab === "cb" && <div className="card">
        <div style={{ marginBottom: 12 }}><label>Bank / Cash account </label>
          <select value={cbAcct} onChange={e => setCbAcct(e.target.value)}>{banks.map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
        {cb && <div>
          <p>Opening balance: <b>{money(cb.opening)}</b></p>
          <table className="full"><thead><tr><th>Date</th><th>Ref</th><th>Description</th><th className="num">Receipts</th><th className="num">Payments</th><th className="num">Balance</th></tr></thead>
            <tbody>{cb.lines.map((l, i) => <tr key={i}><td>{l.date}</td><td className="pill">{l.entryNo}</td><td>{l.description}</td><td className="num">{l.receipts ? money(l.receipts) : ""}</td><td className="num cr">{l.payments ? money(l.payments) : ""}</td><td className="num">{money(l.balance)}</td></tr>)}
              <tr className="tot"><td></td><td></td><td>Period totals</td><td className="num">{money(cb.totalReceipts)}</td><td className="num cr">{money(cb.totalPayments)}</td><td className="num">{money(cb.closing)}</td></tr></tbody></table></div>}
      </div>}
      {tab === "sst" && <div className="card">
        <div className="row" style={{ marginBottom: 12, alignItems: "flex-end", gap: 10 }}>
          <div><label>From</label><br /><input type="date" value={sstFrom} onChange={e => setSstFrom(e.target.value)} /></div>
          <div><label>To</label><br /><input type="date" value={sstTo} onChange={e => setSstTo(e.target.value)} /></div>
          <button className="btn" onClick={loadSst}>Generate</button>
          <button className="btn ghost" onClick={() => { const q = new URLSearchParams(); if (sstFrom) q.set("from", sstFrom); if (sstTo) q.set("to", sstTo); window.location.href = "/api/reports/sst-return/export" + (q.toString() ? "?" + q.toString() : ""); }}>⬇ Excel</button>
        </div>
        {sst && <div>
          <h3 style={{ marginTop: 0 }}>Output tax <span className="muted" style={{ fontWeight: 400 }}>— taxable supplies (sales)</span></h3>
          <table className="full"><thead><tr><th>Code</th><th>Description</th><th>Type</th><th className="num">Rate %</th><th className="num">Taxable value</th><th className="num">Tax</th></tr></thead>
            <tbody>{sst.output.map((r, i) => <tr key={"o" + i}><td className="pill">{r.code}</td><td>{r.name}</td><td>{SSTTYPE[r.taxType] || r.taxType}</td><td className="num">{r.rate}</td><td className="num">{money(r.taxableValue)}</td><td className="num">{money(r.taxAmount)}</td></tr>)}
              {sst.output.length === 0 && <tr><td className="muted" colSpan="6">No output tax in this period.</td></tr>}
              <tr className="tot"><td></td><td>Total output tax</td><td></td><td></td><td className="num">{money(sst.outputTaxable)}</td><td className="num">{money(sst.outputTax)}</td></tr></tbody></table>
          <h3 style={{ marginTop: 18 }}>Input tax <span className="muted" style={{ fontWeight: 400 }}>— purchases (claimable where applicable)</span></h3>
          <table className="full"><thead><tr><th>Code</th><th>Description</th><th>Type</th><th className="num">Rate %</th><th className="num">Taxable value</th><th className="num">Tax</th></tr></thead>
            <tbody>{sst.input.map((r, i) => <tr key={"in" + i}><td className="pill">{r.code}</td><td>{r.name}</td><td>{SSTTYPE[r.taxType] || r.taxType}</td><td className="num">{r.rate}</td><td className="num">{money(r.taxableValue)}</td><td className="num">{money(r.taxAmount)}</td></tr>)}
              {sst.input.length === 0 && <tr><td className="muted" colSpan="6">No input tax in this period.</td></tr>}
              <tr className="tot"><td></td><td>Total input tax</td><td></td><td></td><td className="num">{money(sst.inputTaxable)}</td><td className="num">{money(sst.inputTax)}</td></tr></tbody></table>
          <table className="full" style={{ marginTop: 14 }}><tbody>
            <tr className="tot"><td>Output tax (SST payable)</td><td className="num">{money(sst.outputTax)}</td></tr>
            <tr><td>Less: claimable input tax</td><td className="num">- {money(sst.inputTax)}</td></tr>
            <tr className="tot"><td><b>Net tax payable</b></td><td className="num"><b>{money(sst.netTaxPayable)}</b></td></tr></tbody></table>
          <p className="muted" style={{ marginTop: 8 }}>Period {sst.from} → {sst.to}. SST is single-stage — deduct input tax only where the treatment actually allows it.</p>
          <div className="card" style={{ marginTop: 14, background: "var(--warning-soft)" }}>
            <b>Settle this return</b>
            <p className="muted" style={{ margin: "4px 0 8px" }}>Posts one journal that clears the SST control accounts and records the net {money(sst.netTaxPayable)} {sst.netTaxPayable < 0 ? "refund" : "payment"} through the bank. Run once, after filing.</p>
            <div className="row" style={{ alignItems: "flex-end", gap: 10 }}>
              <div><label>Bank / cash</label><br /><select value={settleBank} onChange={e => setSettleBank(e.target.value)}><option value="">—</option>{banks.map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></div>
              <div><label>Payment date</label><br /><input type="date" value={settleDate} onChange={e => setSettleDate(e.target.value)} /></div>
              <button className="btn" disabled={!settleBank} onClick={settleSst}>Post settlement</button>
              {settleMsg && <span className="muted">{settleMsg}</span>}
            </div>
          </div>
        </div>}
      </div>}
    </div>
  );
}

function Settings({ company, onSaved }) {
  const [accs] = useLoad(() => API.get("/accounts"));
  const [f, setF] = useState({ name: company.name || "", regNo: company.regNo || "", tin: company.tin || "", sstRegNo: company.sstRegNo || "", msicCode: company.msicCode || "", email: company.email || "", phone: company.phone || "", address: company.address || "", baseCurrency: company.baseCurrency || "MYR" });
  const [msg, setMsg] = useState(null);
  const [ob, setOb] = useState([{ accountId: "", debit: 0, credit: 0 }, { accountId: "", debit: 0, credit: 0 }]);
  const [obMsg, setObMsg] = useState(null);
  const F = (k, label) => <div style={{ flex: 1 }}><label>{label}</label><input value={f[k]} onChange={e => setF({ ...f, [k]: e.target.value })} /></div>;
  const save = async () => { try { await API.put("/company", f); setMsg("Saved."); onSaved && onSaved(); } catch (e) { setMsg(e.message); } };
  const syncConnect = async () => {
    setMsg(null);
    try {
      const co = await API.post("/company/sync-connect", {});
      setF({ ...f, name: co.name || f.name, email: co.email || f.email, phone: co.phone || f.phone, regNo: co.regNo || f.regNo, tin: co.tin || f.tin, sstRegNo: co.sstRegNo || f.sstRegNo, address: co.address || f.address });
      setMsg("Synced from OneBloc Connect.");
      onSaved && onSaved();
    } catch (e) { setMsg(e.message); }
  };
  const upd = (i, k, v) => { const n = ob.slice(); n[i] = { ...n[i], [k]: v }; setOb(n); };
  const td = ob.reduce((s, r) => s + Number(r.debit || 0), 0), tc = ob.reduce((s, r) => s + Number(r.credit || 0), 0);
  const postOb = async () => { setObMsg(null); try { await API.post("/opening-balances", { date: today(), lines: ob.filter(r => r.accountId).map(r => ({ accountId: r.accountId, debit: Number(r.debit), credit: Number(r.credit) })) }); setObMsg("Opening balances posted."); setOb([{ accountId: "", debit: 0, credit: 0 }, { accountId: "", debit: 0, credit: 0 }]); } catch (e) { setObMsg(e.message); } };
  return (
    <div>
      <div className="head"><div><h1>Settings</h1><div className="sub">Company profile &amp; opening balances</div></div></div>
      <div className="card"><h3>Company profile <span className="muted" style={{ fontWeight: 400 }}>— used on invoices &amp; e-Invoice</span></h3><div className="form">
        {F("name", "Company name")}
        <div className="row">{F("regNo", "SSM reg no.")}{F("tin", "TIN")}</div>
        <div className="row">{F("sstRegNo", "SST reg no.")}{F("msicCode", "MSIC code")}</div>
        <div className="row">{F("email", "Email")}{F("phone", "Phone")}</div>
        {F("address", "Address")}
        <div className="row" style={{ alignItems: "center" }}><button className="btn" onClick={save}>Save</button><button className="btn ghost" onClick={syncConnect}>⟳ Sync from OneBloc Connect</button>{msg && <span className="muted">{msg}</span>}</div>
      </div></div>
      <div className="card"><h3>Opening balances</h3><p className="muted">Seed a migrating company's balances (debits = credits). Control accounts like AR/AP are allowed here.</p>
        <table className="full"><thead><tr><th>Account</th><th className="num">Debit</th><th className="num">Credit</th><th></th></tr></thead>
          <tbody>{ob.map((r, i) => <tr key={i}>
            <td><select value={r.accountId} onChange={e => upd(i, "accountId", e.target.value)}><option value="">—</option>{(accs || []).map(a => <option key={a.id} value={a.id}>{a.code} {a.name}</option>)}</select></td>
            <td className="num"><input type="number" style={{ width: 110 }} value={r.debit} onChange={e => upd(i, "debit", e.target.value)} /></td>
            <td className="num"><input type="number" style={{ width: 110 }} value={r.credit} onChange={e => upd(i, "credit", e.target.value)} /></td>
            <td>{ob.length > 2 && <button className="btn sm ghost" onClick={() => setOb(ob.filter((_, j) => j !== i))}>✕</button>}</td></tr>)}
            <tr className="tot"><td>{td === tc ? <span className="badge ok">balanced</span> : <span className="badge bad">off by {money(Math.abs(td - tc))}</span>}</td><td className="num">{money(td)}</td><td className="num">{money(tc)}</td><td></td></tr></tbody></table>
        <div className="row" style={{ marginTop: 10, alignItems: "center" }}><button className="btn sm ghost" onClick={() => setOb([...ob, { accountId: "", debit: 0, credit: 0 }])}>+ Add line</button><button className="btn" onClick={postOb}>Post opening balances</button>{obMsg && <span className="muted">{obMsg}</span>}</div>
      </div>
    </div>
  );
}

// OneBloc DataGrid — thin React wrapper around the shared, framework-agnostic
// datagrid.js (window.OneBlocGrid), the SAME component the WebForms apps use.
// Each grid is just a config; data/paging/sort/search/filter come from POST /api/grid/{key}.
function DataGrid({ cfg }) {
  const ref = React.useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el || !window.OneBlocGrid) return;
    el.innerHTML = "";
    window.OneBlocGrid(el, cfg);
  }, [cfg]);
  return <div ref={ref}></div>;
}

const GRIDS = {
  invoices: {
    endpoint: "/api/grid/invoices", gridKey: "invoices", title: "Invoices", subtitle: "Accounts receivable · sales invoices",
    searchHint: "Search by invoice no or customer…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Voided"], openStatuses: ["Posted"],
    dateFilter: { key: "Date", label: "Invoice date" }, amountFilter: { key: "Total", label: "Amount" },
    columns: [{ type: "rowno", label: "#" }, { key: "InvoiceNo", label: "Invoice No", type: "idcode", sortable: true },
      { key: "Customer", label: "Customer", type: "avatar", sortable: true }, { key: "Date", label: "Date", type: "date", sortable: true },
      { key: "DueDate", label: "Due", type: "date", sortable: true, due: true }, { key: "Total", label: "Total", type: "currency", cls: "num", sortable: true },
      { key: "BalanceDue", label: "Balance", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  bills: {
    endpoint: "/api/grid/bills", gridKey: "bills", title: "Bills", subtitle: "Accounts payable · purchase bills",
    searchHint: "Search by bill no or supplier…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Voided"], openStatuses: ["Posted"],
    dateFilter: { key: "Date", label: "Bill date" }, amountFilter: { key: "Total", label: "Amount" },
    columns: [{ type: "rowno", label: "#" }, { key: "BillNo", label: "Bill No", type: "idcode", sortable: true },
      { key: "Supplier", label: "Supplier", type: "avatar", sortable: true }, { key: "Date", label: "Date", type: "date", sortable: true },
      { key: "DueDate", label: "Due", type: "date", sortable: true, due: true }, { key: "Total", label: "Total", type: "currency", cls: "num", sortable: true },
      { key: "BalanceDue", label: "Balance", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  journals: {
    endpoint: "/api/grid/journals", gridKey: "journals", title: "Journals", subtitle: "General ledger · manual & posted entries",
    searchHint: "Search by entry no or description…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Reversed"],
    dateFilter: { key: "Date", label: "Entry date" },
    columns: [{ type: "rowno", label: "#" }, { key: "EntryNo", label: "Entry No", type: "idcode", sortable: true },
      { key: "Date", label: "Date", type: "date", sortable: true }, { key: "Description", label: "Description", type: "text", sortable: true },
      { key: "TotalDebit", label: "Debit", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  receipts: {
    endpoint: "/api/grid/receipts", gridKey: "receipts", title: "Receipts", subtitle: "Money received from customers",
    searchHint: "Search by receipt no or customer…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Voided"],
    dateFilter: { key: "Date", label: "Receipt date" }, amountFilter: { key: "Amount", label: "Amount" },
    columns: [{ type: "rowno", label: "#" }, { key: "ReceiptNo", label: "Receipt No", type: "idcode", sortable: true },
      { key: "Customer", label: "Customer", type: "avatar", sortable: true }, { key: "Date", label: "Date", type: "date", sortable: true },
      { key: "Amount", label: "Amount", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  payments: {
    endpoint: "/api/grid/payments", gridKey: "payments", title: "Payments", subtitle: "Money paid to suppliers",
    searchHint: "Search by payment no or supplier…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Voided"],
    dateFilter: { key: "Date", label: "Payment date" }, amountFilter: { key: "Amount", label: "Amount" },
    columns: [{ type: "rowno", label: "#" }, { key: "PaymentNo", label: "Payment No", type: "idcode", sortable: true },
      { key: "Supplier", label: "Supplier", type: "avatar", sortable: true }, { key: "Date", label: "Date", type: "date", sortable: true },
      { key: "Amount", label: "Amount", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  customers: {
    endpoint: "/api/grid/customers", gridKey: "customers", title: "Customers", subtitle: "Debtors master",
    searchHint: "Search by code, name or email…", rowKey: "Id", sortBy: "Name", sortDir: "ASC", currency: "RM",
    statusField: "AccountStatus", statusOptions: ["Active", "Inactive"],
    columns: [{ type: "rowno", label: "#" }, { key: "Code", label: "Code", type: "idcode", sortable: true },
      { key: "Name", label: "Name", type: "avatar", subKey: "Email", sortable: true }, { key: "Phone", label: "Phone", type: "text", sortable: true },
      { key: "AccountStatus", label: "Status", type: "status", sortable: true }]
  },
  suppliers: {
    endpoint: "/api/grid/suppliers", gridKey: "suppliers", title: "Suppliers", subtitle: "Creditors master",
    searchHint: "Search by code, name or email…", rowKey: "Id", sortBy: "Name", sortDir: "ASC", currency: "RM",
    statusField: "AccountStatus", statusOptions: ["Active", "Inactive"],
    columns: [{ type: "rowno", label: "#" }, { key: "Code", label: "Code", type: "idcode", sortable: true },
      { key: "Name", label: "Name", type: "avatar", subKey: "Email", sortable: true }, { key: "Phone", label: "Phone", type: "text", sortable: true },
      { key: "AccountStatus", label: "Status", type: "status", sortable: true }]
  },
  accounts: {
    endpoint: "/api/grid/accounts", gridKey: "accounts", title: "Chart of Accounts", subtitle: "Ledger accounts",
    searchHint: "Search by code or name…", rowKey: "Id", sortBy: "Code", sortDir: "ASC", currency: "RM",
    statusField: "AccountStatus", statusOptions: ["Active", "Inactive"],
    columns: [{ type: "rowno", label: "#" }, { key: "Code", label: "Code", type: "idcode", sortable: true },
      { key: "Name", label: "Name", type: "text", sortable: true }, { key: "Class", label: "Class", type: "text", sortable: true },
      { key: "Type", label: "Type", type: "text", sortable: true }, { key: "AccountStatus", label: "Status", type: "status", sortable: true }]
  },
  assets: {
    endpoint: "/api/grid/assets", gridKey: "assets", title: "Fixed Assets", subtitle: "Depreciable assets register",
    searchHint: "Search by code or name…", rowKey: "Id", sortBy: "AcquisitionDate", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Active", "Disposed"],
    dateFilter: { key: "AcquisitionDate", label: "Acquired" }, amountFilter: { key: "Cost", label: "Cost" },
    columns: [{ type: "rowno", label: "#" }, { key: "Code", label: "Code", type: "idcode", sortable: true },
      { key: "Name", label: "Name", type: "text", sortable: true }, { key: "Category", label: "Category", type: "text", sortable: true },
      { key: "AcquisitionDate", label: "Acquired", type: "date", sortable: true }, { key: "Cost", label: "Cost", type: "currency", cls: "num", sortable: true },
      { key: "NetBookValue", label: "NBV", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  periods: {
    endpoint: "/api/grid/periods", gridKey: "periods", title: "Fiscal Periods", subtitle: "Accounting periods",
    searchHint: "Search by period name…", rowKey: "Id", sortBy: "StartDate", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Open", "Closed", "Locked"],
    dateFilter: { key: "StartDate", label: "Starts" },
    columns: [{ type: "rowno", label: "#" }, { key: "PeriodNo", label: "No.", type: "text", sortable: true },
      { key: "Name", label: "Period", type: "text", sortable: true }, { key: "Year", label: "FY", type: "text", sortable: true },
      { key: "StartDate", label: "Start", type: "date", sortable: true }, { key: "EndDate", label: "End", type: "date", sortable: true },
      { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  "tax-codes": {
    endpoint: "/api/grid/tax-codes", gridKey: "tax-codes", title: "Tax Codes", subtitle: "SST tax codes",
    searchHint: "Search by code or name…", rowKey: "Id", sortBy: "Code", sortDir: "ASC", currency: "RM",
    statusField: "AccountStatus", statusOptions: ["Active", "Inactive"],
    columns: [{ type: "rowno", label: "#" }, { key: "Code", label: "Code", type: "idcode", sortable: true },
      { key: "Name", label: "Name", type: "text", sortable: true }, { key: "Rate", label: "Rate %", type: "text", cls: "num", sortable: true },
      { key: "Type", label: "Type", type: "text", sortable: true }, { key: "AccountStatus", label: "Status", type: "status", sortable: true }]
  },
  dimensions: {
    endpoint: "/api/grid/dimensions", gridKey: "dimensions", title: "Dimensions", subtitle: "Cost centres · projects · properties",
    searchHint: "Search by code or name…", rowKey: "Id", sortBy: "Code", sortDir: "ASC", currency: "RM",
    statusField: "AccountStatus", statusOptions: ["Active", "Inactive"],
    columns: [{ type: "rowno", label: "#" }, { key: "Code", label: "Code", type: "idcode", sortable: true },
      { key: "Name", label: "Name", type: "text", sortable: true }, { key: "Type", label: "Type", type: "text", sortable: true },
      { key: "AccountStatus", label: "Status", type: "status", sortable: true }]
  },
  "payment-vouchers": {
    endpoint: "/api/grid/payment-vouchers", gridKey: "payment-vouchers", title: "Payment Vouchers", subtitle: "Direct cash out",
    searchHint: "Search by voucher no or payee…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Voided"],
    dateFilter: { key: "Date", label: "Date" }, amountFilter: { key: "Total", label: "Amount" },
    columns: [{ type: "rowno", label: "#" }, { key: "VoucherNo", label: "Voucher No", type: "idcode", sortable: true },
      { key: "Payee", label: "Payee", type: "avatar", sortable: true }, { key: "Date", label: "Date", type: "date", sortable: true },
      { key: "Total", label: "Amount", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  "receipt-vouchers": {
    endpoint: "/api/grid/receipt-vouchers", gridKey: "receipt-vouchers", title: "Receipt Vouchers", subtitle: "Direct cash in",
    searchHint: "Search by voucher no or payer…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Voided"],
    dateFilter: { key: "Date", label: "Date" }, amountFilter: { key: "Total", label: "Amount" },
    columns: [{ type: "rowno", label: "#" }, { key: "VoucherNo", label: "Voucher No", type: "idcode", sortable: true },
      { key: "Payer", label: "Payer", type: "avatar", sortable: true }, { key: "Date", label: "Date", type: "date", sortable: true },
      { key: "Total", label: "Amount", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  },
  "sales-credit-notes": {
    endpoint: "/api/grid/sales-credit-notes", gridKey: "sales-credit-notes", title: "Credit Notes", subtitle: "Sales credit notes",
    searchHint: "Search by CN no or customer…", rowKey: "Id", sortBy: "Date", sortDir: "DESC", currency: "RM",
    statusField: "Status", statusOptions: ["Draft", "Posted", "Voided"],
    dateFilter: { key: "Date", label: "Date" }, amountFilter: { key: "Total", label: "Amount" },
    columns: [{ type: "rowno", label: "#" }, { key: "CreditNoteNo", label: "CN No", type: "idcode", sortable: true },
      { key: "Customer", label: "Customer", type: "avatar", sortable: true }, { key: "Date", label: "Date", type: "date", sortable: true },
      { key: "Total", label: "Amount", type: "currency", cls: "num", sortable: true }, { key: "Status", label: "Status", type: "status", sortable: true }]
  }
};

// CFO controls: approval queue (maker-checker-approver), pre-close checklist, and the KPI dashboard —
// the guide's governance surface. All figures derive from the GL via the API (rule I).
function Controls() {
  const now = new Date();
  const [pending, setPending] = useState(null);
  const [checks, setChecks] = useState(null);
  const [kpis, setKpis] = useState(null);
  const [err, setErr] = useState(null);
  const load = () => {
    API.get("/approvals/pending").then(setPending).catch(e => setErr(e.message));
    API.get(`/close/checklist?year=${now.getFullYear()}&month=${now.getMonth() + 1}`).then(setChecks).catch(() => {});
    API.get(`/kpis?year=${now.getFullYear()}&month=${now.getMonth() + 1}`).then(setKpis).catch(() => {});
  };
  useEffect(load, []);
  const act = (docType, id, action) => API.post(`/approvals/${docType}/${id}/${action}`, {}).then(() => { load(); OBNotify.success(docType + " " + (action === "approve" ? "approved" : action === "reject" ? "returned to draft" : action + "ed") + "."); }).catch(e => setErr(e.message));
  const money = v => v == null ? "—" : "RM " + Number(v).toLocaleString("en-MY", { minimumFractionDigits: 2 });
  return (
    <div>
      <div className="head"><div><h1>Controls</h1><div className="sub">Approvals · close checklist · CFO dashboard</div></div></div>
      {err && <div className="card" style={{ borderColor: "#C8472F", color: "#C8472F", padding: 12, marginBottom: 12 }}>{err}</div>}
      {kpis && <div className="card"><h3>This month</h3><table className="full"><tbody>
        <tr><td>Revenue</td><td className="num">{money(kpis.revenue)}</td><td>Gross margin</td><td className="num">{kpis.grossMarginPct ?? "—"}%</td></tr>
        <tr><td>Net profit</td><td className="num">{money(kpis.netProfit)}</td><td>Net margin</td><td className="num">{kpis.netMarginPct ?? "—"}%</td></tr>
        <tr><td>DSO / DPO</td><td className="num">{kpis.dso ?? "—"} / {kpis.dpo ?? "—"} days</td><td>Current ratio</td><td className="num">{kpis.currentRatio ?? "—"}</td></tr>
        <tr><td>Cash</td><td className="num">{money(kpis.cash)}</td><td>Deferred income</td><td className="num">{money(kpis.deferredIncome)}</td></tr>
      </tbody></table></div>}
      <div className="card"><h3>Awaiting approval</h3>
        {(!pending || pending.length === 0) ? <p className="muted">Nothing waiting.</p> :
          <table className="full"><thead><tr><th>Type</th><th>No.</th><th className="num">Amount</th><th>Maker</th><th></th></tr></thead>
            <tbody>{pending.map(p => <tr key={p.id}>
              <td>{p.docType}</td><td className="pill">{p.docNo}</td><td className="num">{money(p.amount)}</td><td>{p.createdBy || p.submittedBy}</td>
              <td><button className="btn sm" onClick={() => act(p.docType, p.id, "approve")}>Approve</button>{" "}
                  <button className="btn sm ghost" onClick={() => act(p.docType, p.id, "reject")}>Return</button></td>
            </tr>)}</tbody></table>}
      </div>
      {checks && <div className="card"><h3>Pre-close checklist</h3><table className="full">
        <tbody>{checks.map((c, i) => <tr key={i}>
          <td>{c.pass ? "✅" : "❌"} {c.check}</td><td className="muted">{c.detail}</td>
        </tr>)}</tbody></table></div>}
    </div>
  );
}

function App() {
  const [me, setMe] = useState(undefined);
  const [page, setPage] = useState("dashboard");
  const loadMe = () => API.me().then(setMe).catch(() => setMe({ authenticated: false }));
  useEffect(() => { loadMe(); }, []);
  if (me === undefined) return <div className="boot">Loading…</div>;
  if (!me.authenticated) return <Login />;
  const nav = (id, label) => <button className={"nav" + (page === id ? " active" : "")} onClick={() => setPage(id)}>{label}</button>;
  const co = me.company || {};
  return (
    <div className="shell">
      <aside className="side">
        <div className="lock"><img src="/img/onebloc-accounting.png" alt="" width="34" height="34" /><span className="brand"><span className="one">One</span><span className="bloc">Bloc</span><span className="mod">Account</span></span></div>
        {nav("dashboard", "Dashboard")}
        {nav("accounts", "Chart of Accounts")}
        {nav("journals", "Journals")}
        {nav("sales", "Sales")}
        {nav("purchases", "Purchases")}
        {nav("banking", "Banking")}
        {nav("assets", "Fixed Assets")}
        {nav("reports", "Reports")}
        {nav("owner", "Owner Statements")}
        {nav("periods", "Periods & Close")}
        {nav("controls", "Controls")}
        {nav("settings", "Settings")}
        <div style={{ fontSize: ".62rem", fontWeight: 700, letterSpacing: ".09em", textTransform: "uppercase", opacity: .45, margin: "16px 12px 4px" }}>Data Grids</div>
        {nav("g:invoices", "Invoices")}
        {nav("g:bills", "Bills")}
        {nav("g:journals", "Journals")}
        {nav("g:receipts", "Receipts")}
        {nav("g:payments", "Payments")}
        {nav("g:customers", "Customers")}
        {nav("g:suppliers", "Suppliers")}
        {nav("g:accounts", "Chart of Accounts")}
        {nav("g:assets", "Fixed Assets")}
        {nav("g:periods", "Fiscal Periods")}
        {nav("g:tax-codes", "Tax Codes")}
        {nav("g:dimensions", "Dimensions")}
        {nav("g:payment-vouchers", "Payment Vouchers")}
        {nav("g:receipt-vouchers", "Receipt Vouchers")}
        {nav("g:sales-credit-notes", "Credit Notes")}
        <div className="sp"></div>
        <div className="who">{co.name || me.name}<br /><span style={{ opacity: .7 }}>{me.email}</span></div>
        <button className="nav" onClick={() => API.logout()}>Sign out</button>
      </aside>
      <main className="main">
        {page === "dashboard" && <Dashboard />}
        {page === "accounts" && <Accounts />}
        {page === "journals" && <Journals />}
        {page === "sales" && <Sales />}
        {page.indexOf("g:") === 0 && GRIDS[page.slice(2)] && <DataGrid cfg={GRIDS[page.slice(2)]} />}
        {page === "purchases" && <Purchases />}
        {page === "banking" && <Banking />}
        {page === "assets" && <FixedAssets />}
        {page === "reports" && <Reports />}
        {page === "owner" && <OwnerStatements />}
        {page === "periods" && <Periods />}
        {page === "controls" && <Controls />}
        {page === "settings" && <Settings company={co} onSaved={loadMe} />}
      </main>
    </div>
  );
}

function Periods() {
  const [years, , reloadYears] = useLoad(() => API.get("/fiscal-years"));
  const [periods, , reloadPeriods] = useLoad(() => API.get("/periods"));
  const [msg, setMsg] = useState(null);
  const [closing, setClosing] = useState(null);          // { id, code, date } — inline close-year confirm
  const [nf, setNf] = useState({ code: "", start: "", end: "" });

  const reload = () => { reloadYears(); reloadPeriods(); };
  const act = async (fn, ok) => { setMsg(null); try { await fn(); setMsg(ok); reload(); if (ok) OBNotify.success(ok); return true; } catch (e) { setMsg(e.message); return false; } };
  const setStatus = (p, status) => act(() => API.post(`/periods/${p.id}/status`, { status }), `${p.name} → ${PSTATUS[status]}.`);
  const closeYear = async () => {
    if (!(await OBNotify.confirm({ title: "Close fiscal year " + closing.code + "?", message: "Net P&L rolls to Retained Earnings and every period in the year locks. This cannot be undone.", okText: "Close year", danger: true }))) return;
    if (await act(() => API.post(`/fiscal-years/${closing.id}/close`, { closeDate: closing.date }), `Fiscal year ${closing.code} closed — net P&L rolled to Retained Earnings, all periods locked.`)) setClosing(null);
  };
  const createYear = async () => { if (await act(() => API.post("/fiscal-years", { code: nf.code.trim(), start: nf.start, end: nf.end }), `Fiscal year ${nf.code} created.`)) setNf({ code: "", start: "", end: "" }); };
  const byYear = fyId => (periods || []).filter(p => p.fiscalYearId === fyId).sort((a, b) => a.periodNo - b.periodNo);

  return (
    <div>
      <div className="head"><div><h1>Periods &amp; Year-End Close</h1><div className="sub">Lock periods against posting and run the year-end close</div></div></div>
      {msg && <div className="card" style={{ padding: "10px 14px" }}>{msg}</div>}
      {(years || []).map(fy => {
        const open = fy.status === 0;
        const ps = byYear(fy.id);
        return (
          <div className="card" key={fy.id}>
            <div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
              <h3 style={{ margin: 0 }}>{fy.code} <span className="muted" style={{ fontWeight: 400 }}>{fy.startDate} → {fy.endDate}</span></h3>
              <span className="row" style={{ gap: 10, alignItems: "center" }}>
                <span className={"badge " + (open ? "ok" : "bad")}>{open ? "Open" : "Closed"}</span>
                {open && !(closing && closing.id === fy.id) && <button className="btn sm" onClick={() => setClosing({ id: fy.id, code: fy.code, date: fy.endDate })}>Close year…</button>}
              </span>
            </div>
            {closing && closing.id === fy.id &&
              <div className="card" style={{ margin: "12px 0 4px", background: "var(--warning-soft)" }}>
                <p style={{ marginTop: 0 }}><b>Close {fy.code}?</b> Posts one balanced closing journal that rolls every Income/Expense balance into Retained Earnings, then <b>locks all {ps.length} periods</b>. This cannot be undone from here.</p>
                <div className="row" style={{ alignItems: "center", gap: 10 }}>
                  <label>Close date <input type="date" value={closing.date} onChange={e => setClosing({ ...closing, date: e.target.value })} /></label>
                  <button className="btn" onClick={closeYear}>Confirm close</button>
                  <button className="btn ghost" onClick={() => setClosing(null)}>Cancel</button>
                </div>
              </div>}
            <table className="full" style={{ marginTop: 10 }}>
              <thead><tr><th>#</th><th>Period</th><th>From</th><th>To</th><th>Status</th><th></th></tr></thead>
              <tbody>{ps.map(p => <tr key={p.id}>
                <td>{p.periodNo}</td><td>{p.name}</td><td>{p.startDate}</td><td>{p.endDate}</td>
                <td><span className={"badge " + PBADGE[p.status]}>{PSTATUS[p.status]}</span></td>
                <td className="num">
                  {p.status === 0 && <span className="row" style={{ gap: 6, justifyContent: "flex-end" }}><button className="btn sm ghost" onClick={() => setStatus(p, 1)}>Close</button><button className="btn sm ghost" onClick={() => setStatus(p, 2)}>Lock</button></span>}
                  {p.status === 1 && <span className="row" style={{ gap: 6, justifyContent: "flex-end" }}><button className="btn sm ghost" onClick={() => setStatus(p, 0)}>Reopen</button><button className="btn sm ghost" onClick={() => setStatus(p, 2)}>Lock</button></span>}
                  {p.status === 2 && <span className="muted">sealed</span>}
                </td>
              </tr>)}</tbody>
            </table>
          </div>
        );
      })}
      <div className="card">
        <h3>New fiscal year</h3>
        <p className="muted">Creates the year and its monthly periods (all Open). Posting an entry requires a period to exist for its date.</p>
        <div className="row" style={{ alignItems: "flex-end", gap: 10 }}>
          <label>Code <input value={nf.code} placeholder="FY2027" onChange={e => setNf({ ...nf, code: e.target.value })} /></label>
          <label>Start <input type="date" value={nf.start} onChange={e => setNf({ ...nf, start: e.target.value })} /></label>
          <label>End <input type="date" value={nf.end} onChange={e => setNf({ ...nf, end: e.target.value })} /></label>
          <button className="btn" onClick={createYear} disabled={!nf.code || !nf.start || !nf.end}>Create</button>
        </div>
      </div>
    </div>
  );
}

function OwnerStatements() {
  const [dims, , reloadDims] = useLoad(() => API.get("/dimensions"));
  const properties = (dims || []).filter(d => d.type === 2);
  const [sel, setSel] = useState("");
  const [from, setFrom] = useState("");
  const [to, setTo] = useState("");
  const [fee, setFee] = useState(20);
  const [st, setSt] = useState(null);
  const [msg, setMsg] = useState(null);
  const [np, setNp] = useState({ code: "", name: "" });

  const run = (id) => {
    const pid = id || sel;
    if (!pid) { setSt(null); return; }
    const qs = new URLSearchParams();
    if (from) qs.set("from", from);
    if (to) qs.set("to", to);
    qs.set("feePercent", fee || 0);
    API.get(`/reports/owner-statement/${pid}?${qs.toString()}`).then(s => { setSt(s); setMsg(null); }).catch(e => { setSt(null); setMsg(e.message); });
  };
  const addProperty = async () => {
    setMsg(null);
    try { await API.post("/dimensions", { code: np.code.trim(), name: np.name.trim(), type: 2 }); setNp({ code: "", name: "" }); reloadDims(); setMsg("Property added."); }
    catch (e) { setMsg(e.message); }
  };
  useEffect(() => { if (!sel && properties.length) { setSel(properties[0].id); run(properties[0].id); } }, [dims]);

  return (
    <div>
      <div className="head"><div><h1>Owner Statements</h1><div className="sub">Per-property P&amp;L and net owner payout</div></div></div>
      {msg && <div className="card" style={{ padding: "10px 14px" }}>{msg}</div>}
      <div className="card">
        <div className="row" style={{ alignItems: "flex-end", gap: 12, flexWrap: "wrap" }}>
          <div><label>Property</label><br /><select value={sel} onChange={e => { setSel(e.target.value); run(e.target.value); }}>{properties.length === 0 && <option value="">(no properties yet)</option>}{properties.map(d => <option key={d.id} value={d.id}>{d.code} {d.name}</option>)}</select></div>
          <div><label>From</label><br /><input type="date" value={from} onChange={e => setFrom(e.target.value)} /></div>
          <div><label>To</label><br /><input type="date" value={to} onChange={e => setTo(e.target.value)} /></div>
          <div><label>Mgmt fee %</label><br /><input type="number" style={{ width: 80 }} value={fee} onChange={e => setFee(e.target.value)} /></div>
          <button className="btn" onClick={() => run()} disabled={!sel}>Generate</button>
          <button className="btn ghost" disabled={!sel} onClick={() => { if (!sel) return; const q = new URLSearchParams(); if (from) q.set("from", from); if (to) q.set("to", to); q.set("feePercent", fee || 0); window.location.href = "/api/reports/owner-statement/" + sel + "/export?" + q.toString(); }}>⬇ Excel</button>
        </div>
      </div>
      {st && <div className="card">
        <h3 style={{ marginTop: 0 }}>{st.propertyCode} {st.propertyName} <span className="muted" style={{ fontWeight: 400 }}>{st.from} → {st.to}</span></h3>
        <table className="full"><tbody>
          <tr className="tot"><td colSpan="2"><b>Revenue</b></td></tr>
          {st.income.map((r, i) => <tr key={"i" + i}><td>{r.code} {r.name}</td><td className="num">{money(r.amount)}</td></tr>)}
          {st.income.length === 0 && <tr><td className="muted" colSpan="2">No revenue tagged to this property in range.</td></tr>}
          <tr className="tot"><td>Total revenue</td><td className="num">{money(st.totalIncome)}</td></tr>
          <tr className="tot"><td colSpan="2" style={{ paddingTop: 14 }}><b>Expenses</b></td></tr>
          {st.expenses.map((r, i) => <tr key={"e" + i}><td>{r.code} {r.name}</td><td className="num">{money(r.amount)}</td></tr>)}
          {st.expenses.length === 0 && <tr><td className="muted" colSpan="2">No expenses tagged to this property in range.</td></tr>}
          <tr className="tot"><td>Total expenses</td><td className="num">{money(st.totalExpenses)}</td></tr>
          <tr className="tot"><td>Net operating profit</td><td className="num">{money(st.netOperating)}</td></tr>
          <tr><td>Management fee ({st.feePercent}% of {st.feeBasis.toLowerCase()})</td><td className="num">- {money(st.managementFee)}</td></tr>
          <tr className="tot"><td><b>Net owner payout</b></td><td className="num"><b>{money(st.netOwnerPayout)}</b></td></tr>
        </tbody></table>
      </div>}
      <div className="card">
        <h3>Properties</h3>
        <p className="muted">Each property is a reporting dimension. Tag journal lines (revenue &amp; expenses) with a property, then generate its statement above.</p>
        <table className="full"><thead><tr><th>Code</th><th>Name</th></tr></thead>
          <tbody>{properties.map(d => <tr key={d.id}><td className="pill">{d.code}</td><td>{d.name}</td></tr>)}
            {properties.length === 0 && <tr><td className="muted" colSpan="2">No properties yet — add one below.</td></tr>}</tbody></table>
        <div className="row" style={{ marginTop: 10, alignItems: "flex-end", gap: 10 }}>
          <div><label>Code</label><br /><input value={np.code} placeholder="UNIT-01" onChange={e => setNp({ ...np, code: e.target.value })} /></div>
          <div><label>Name</label><br /><input value={np.name} placeholder="KLCC Suite 12A" onChange={e => setNp({ ...np, name: e.target.value })} /></div>
          <button className="btn sm" onClick={addProperty} disabled={!np.code || !np.name}>Add property</button>
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
