// HR module
const ALL_DEPTS = "All departments"; // sentinel for the time-logs department filter

function HR() {
  const { useState, useEffect, useRef } = React;
  const [tab, setTab] = useState("people");
  // Everything starts empty and fills from the server — HR has no offline data.
  const [leaves, setLeaves] = useState([]);
  const [employees, setEmployees] = useState([]);
  const [query, setQuery] = useState("");
  const [showAdd, setShowAdd] = useState(false);
  const [sel, setSel] = useState(null);
  const [selContract, setSelContract] = useState(null); // employee id whose contract is open
  const [editReq, setEditReq] = useState(null);
  const [adjustments, setAdjustments] = useState([]);
  const [showAdjust, setShowAdjust] = useState(false);
  const [timePage, setTimePage] = useState(1);
  const [timeFrom, setTimeFrom] = useState("");
  const [timeTo, setTimeTo] = useState("");
  const [timeDept, setTimeDept] = useState(ALL_DEPTS); // time logs: department filter
  const [decideFor, setDecideFor] = useState(null); // { leave, action }
  const [leaveView, setLeaveView] = useState("all"); // requests table filter: all | pending | decided
  const [payView, setPayView] = useState("payments");
  const [payQ, setPayQ] = useState("");
  const [payStatus, setPayStatusState] = useState({});
  const [payDates, setPayDates] = useState({});
  const [advances, setAdvances] = useState([]);
  const [showAdvance, setShowAdvance] = useState(false);
  const [repayAdv, setRepayAdv] = useState(null);
  const [payEdit, setPayEdit] = useState(null); // employee being pay-edited
  const [payTx, setPayTx] = useState({});
  const [payRecs, setPayRecs] = useState({}); // empId -> server-computed record (current month)
  const [payHistory, setPayHistory] = useState([]); // past runs, grouped by period
  const [payOpen, setPayOpen] = useState(null); // expanded salary row (emp id)
  const [recordFor, setRecordFor] = useState(null); // employee getting a payment recorded
  const [adjEdit, setAdjEdit] = useState(null); // employee in the bonuses/deductions editor

  const [apiContracts, setApiContracts] = useState({}); // empId -> contract
  // Contracts load beside employees, so "no contract" is only trustworthy once
  // that call has answered — before it, an empty map would flag everyone.
  const [contractsLoaded, setContractsLoaded] = useState(false);
  const [apiTimeRows, setApiTimeRows] = useState([]);
  const [auditRows, setAuditRows] = useState([]);

  useEffect(() => {
    D.leaves = []; D.advances = []; D.payAdjustments = [];
    API.employees.listAll()
      .then((list) => { setEmployees(list); D.employees = list; })
      .catch((ex) => notify("Couldn't load employees from the server — " + ex.message));
    API.leaves.list().then((ls) => { setLeaves(ls); D.leaves = ls; }).catch(() => {});
    API.pay.adjustments.list().then((as) => { setAdjustments(as); D.payAdjustments = as; }).catch(() => {});
    API.pay.advances.list().then((as) => { setAdvances(as); D.advances = as; }).catch(() => {});
    reloadContracts();
    API.timeLogs.list().then(setApiTimeRows).catch(() => {});
    API.audit.list().then((rows) => { D.auditLog = rows; setAuditRows(rows); }).catch(() => {});
    reloadPayments();
    reloadRecords();
  }, []);

  const reloadContracts = () => {
    API.contracts.list().then((cs) => {
      const map = {}; cs.forEach((c) => { map[c.empId] = c; });
      setApiContracts(map);
      setContractsLoaded(true);
    }).catch(() => {});
  };
  const reloadPayments = () => {
    API.pay.payments.list().then((txs) => {
      const grouped = {};
      txs.forEach((t) => { (grouped[t.empId] = grouped[t.empId] || []).push({ date: t.date, amount: t.amount, method: t.method, ref: t.ref }); });
      setPayTx(grouped); D.payTx = grouped;
    }).catch(() => {});
  };
  const reloadRecords = () => {
    API.pay.records.list().then((rs) => {
      const st = {}, dates = {}, recs = {};
      rs.filter((r) => r.period === API.fmt.period()).forEach((r) => {
        st[r.empId] = r.status;
        if (r.paidOn) dates[r.empId] = r.paidOn;
        if (r.computed) recs[r.empId] = r; // server-computed payroll record for this month
      });
      setPayStatusState(st); D.payStatus = st;
      setPayDates(dates); D.payDates = dates;
      setPayRecs(recs);
      // Past runs: every period the server holds records for, newest first.
      const byPeriod = {};
      rs.forEach((r) => {
        const g = (byPeriod[r.period] = byPeriod[r.period] || { period: r.period, employees: 0, total: 0, unpaid: 0 });
        g.employees += 1; g.total += r.net;
        if (r.status !== "Paid") g.unpaid += 1;
      });
      const hist = Object.values(byPeriod)
        .sort((a, b) => (a.period < b.period ? 1 : -1))
        .map((g) => ({ period: g.period, employees: g.employees, total: g.total, status: g.unpaid ? "Open" : "Paid" }));
      setPayHistory(hist); D.payrollHistory = hist;
    }).catch(() => {});
  };
  const reloadAudit = () => {
    API.audit.list().then((rows) => { D.auditLog = rows; setAuditRows(rows); }).catch(() => {});
  };

  const MONTH_FULL = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
  // Month name comes from Intl so payroll periods follow the active language.
  const periodLabel = (p) => {
    const x = String(p).split("-");
    return new Date(+x[0] || 2026, (+x[1] || 1) - 1, 1)
      .toLocaleDateString(uiLocale(), { month: "long", year: "numeric" });
  };
  const curPeriod = API.fmt.period();
  const curLabel = periodLabel(curPeriod);

  const headcount = employees.length;
  const deptCount = new Set(employees.map((e) => e.dept).filter(Boolean)).size;
  const onLeaveNames = employees.filter((e) => e.status === "On leave").map((e) => e.name).join(", ");
  const basePayroll = employees.reduce((s, e) => s + e.salary, 0);
  const adjustTotal = adjustments.reduce((s, a) => s + (a.kind === "Deduction" ? -a.amount : a.amount), 0);
  const allowancesTotal = employees.reduce((s, e) => s + ((apiContracts[e.id] || {}).allowances || []).reduce((x, a) => x + a.amt, 0), 0);
  const payroll = basePayroll + allowancesTotal + adjustTotal;
  const onLeave = employees.filter((e) => e.status === "On leave").length;
  const pending = leaves.filter((l) => l.status === "Pending");

  const decide = (id, status, note) => {
    setLeaves((ls) => ls.map((l) => (l.id === id ? { ...l, status, note: note || "" } : l)));
    notify("Leave request " + (status === "Approved" ? "approved" : "declined"));
    API.leaves.decide(id, status, note).then(reloadAudit).catch((ex) => notify("Couldn't save decision \u2014 " + ex.message));
  };

  // Hiring is two calls — the employee row, then the portal login. When the login
  // is rejected (a taken username) the row already exists, so it is parked here:
  // the retry re-saves that same row instead of hiring a second person.
  const pendingHire = useRef(null);

  // Rejects on failure. AddEmployeeModal stays open and puts the message on the
  // field that caused it, so nothing here is swallowed by a toast.
  const addEmployee = async (emp) => {
    const pending = pendingHire.current;
    const rec = pending ? await API.employees.update(pending.id, emp) : await API.employees.create(emp);
    pendingHire.current = rec;
    setEmployees((es) => {
      const next = pending ? es.map((e) => (e.id === rec.id ? rec : e)) : [...es, rec];
      D.employees = next;
      return next;
    });
    if ((emp.accountUsername || emp.accountEmail) && emp.accountPassword) {
      await API.employees.createAccount(rec.id, { username: emp.accountUsername, email: emp.accountEmail, password: emp.accountPassword });
    }
    pendingHire.current = null;
    setShowAdd(false);
    // Hiring never opens a contract — flag the gap so it is added deliberately.
    notify(T("hr.addedNoContract", { name: rec.name }));
    reloadAudit();
  };

  // Closing after the row saved but the login didn't: the employee is on the list
  // either way, so say so instead of leaving the half-finished hire unmentioned.
  const closeAdd = () => {
    const abandoned = pendingHire.current;
    pendingHire.current = null;
    setShowAdd(false);
    if (abandoned) {
      notify(T("hr.savedWithoutLogin", { name: abandoned.name }));
      reloadAudit();
    }
  };

  const addAdjustment = (a) => {
    const target = employees.find((e) => e.name === a.emp);
    if (!target) { notify("Unknown employee"); return; }
    API.pay.adjustments.create({ ...a, empId: target.id })
        .then((row) => {
          const next = [...adjustments, row];
          setAdjustments(next); D.payAdjustments = next;
          setShowAdjust(false);
          notify(a.kind + " of " + money(a.amount) + " added for " + a.emp);
          reloadAudit();
        })
      .catch((ex) => notify("Couldn't save adjustment — " + ex.message));
  };

  const updateEmployee = (p) => {
    API.employees.update(p.id, p)
        .then((rec) => {
          setEmployees((es) => { const next = es.map((e) => (e.id === rec.id ? rec : e)); D.employees = next; return next; });
          setSel(rec);
        })
      .catch((ex) => notify("Couldn't save changes — " + ex.message));
  };

  // ---- payroll financials ----
  // The server-computed payroll record (payroll run) is the source of truth when
  // present; the local math covers the window before this month's run.
  const recFor = (e) => payRecs[e.id] || null;
  const bonusFor = (id) => adjustments.filter((a) => a.empId === id && a.kind !== "Deduction").reduce((s, a) => s + a.amount, 0);
  const dedFor = (id) => adjustments.filter((a) => a.empId === id && a.kind === "Deduction").reduce((s, a) => s + a.amount, 0);
  const advRepayFor = (id) => advances.filter((v) => v.empId === id && v.status === "Active").reduce((s, v) => s + Math.min(v.monthly, v.amount - v.repaid), 0);
  const advFor = (e) => { const r = recFor(e); return r ? r.advance : advRepayFor(e.id); };
  const allowFor = (e) => ((apiContracts[e.id] || {}).allowances || []).reduce((s, a) => s + a.amt, 0);
  const netFor = (e) => { const r = recFor(e); return r ? r.net : e.salary + allowFor(e) + bonusFor(e.id) - dedFor(e.id) - advRepayFor(e.id); };
  const statusFor = (e) => {
    const r = recFor(e);
    if (r) return r.status;
    const paid = txFor(e).reduce((s, t) => s + t.amount, 0);
    if (paid >= netFor(e)) return "Paid";
    if (paid > 0) return "Partial";
    return payStatus[e.id] === "Paid" ? "Pending" : (payStatus[e.id] || "Pending");
  };
  const txFor = (e) => payTx[e.id] || [];
  const paidAmt = (e) => txFor(e).reduce((s, t) => s + t.amount, 0);
  const addTx = (e, amount, method) => {
    const nowPaid = paidAmt(e) + amount >= netFor(e);
    notify(money(amount) + " payment recorded for " + e.name + (nowPaid ? " \u00b7 fully paid" : ""));
    API.pay.payments.create(e.id, amount, method)
      .then(() => {
        // the server recomputes the payroll record on every payment
        reloadPayments();
        reloadRecords();
        reloadAudit();
      })
        .catch((ex) => notify("Couldn't save payment \u2014 " + ex.message));
  };
  const markPaid = (e) => {
    const rem = netFor(e) - paidAmt(e);
    if (rem > 0) { addTx(e, rem, "Bank transfer"); return; }
    API.pay.records.refresh(e.id).then(reloadRecords).catch(() => {});
  };
  const runPayroll = () => {
    API.pay.records.run()
      .then((rs) => {
        const month = rs.filter((r) => r.period === API.fmt.period());
        reloadRecords();
        // the run posts advance installments, so balances changed too
        API.pay.advances.list().then((as) => { setAdvances(as); D.advances = as; }).catch(() => {});
        reloadAudit();
        notify(T("hr.payrollRunComplete", { n: month.length }));
      })
      .catch((ex) => notify(T("hr.payrollRunFailed", { message: ex.message })));
  };
  const issueAdvance = (a) => {
    const target = employees.find((e) => e.name === a.emp);
    if (!target) { notify("Unknown employee"); return; }
    API.pay.advances.create({ ...a, empId: target.id })
        .then((adv) => {
          const next = [adv, ...advances];
          setAdvances(next); D.advances = next;
          setShowAdvance(false);
          notify("Advance of " + money(a.amount) + " issued to " + a.emp);
          reloadAudit();
        })
        .catch((ex) => notify("Couldn't issue advance \u2014 " + ex.message));
  };
  const recordRepay = (amount) => {
    API.pay.advances.repay(repayAdv.id, amount)
        .then((adv) => {
          const next = advances.map((v) => (v.id === adv.id ? adv : v));
          setAdvances(next); D.advances = next;
          notify(money(amount) + " repayment recorded for " + repayAdv.emp);
          setRepayAdv(null);
          reloadAudit();
        })
        .catch((ex) => notify("Couldn't record repayment \u2014 " + ex.message));
  };
  const paidCount = employees.filter((e) => statusFor(e) === "Paid").length;
  const computedCount = Object.keys(payRecs).length;
  const applyPayEdit = (r) => {
    const emp = payEdit;
    // Status and paid-on are computed server-side; only the salary is editable.
    if (r.salary === emp.salary) { setPayEdit(null); return; }
    // Salary lives on the contract — the server refuses to invent one, so say why here.
    if (noContract(emp.id)) { notify(T("hr.needContractForSalary", { name: emp.name })); setPayEdit(null); return; }
    API.employees.update(emp.id, { ...emp, salary: r.salary, setSalary: true })
      .then((rec) => {
        setEmployees((es) => { const next = es.map((e) => (e.id === rec.id ? rec : e)); D.employees = next; return next; });
        return API.pay.records.refresh(emp.id);
      })
      .then(() => { reloadRecords(); reloadAudit(); notify(emp.name + "'s pay record updated"); })
      .catch((ex) => notify("Couldn't save salary \u2014 " + ex.message));
    setPayEdit(null);
  };
  const applyAdjust = (r) => {
    const emp = adjEdit;
    API.pay.adjustments.replace(emp.id, r.adjs)
      .then((rows) => {
        const merged = [...adjustments.filter((a) => a.empId !== emp.id), ...rows];
        setAdjustments(merged); D.payAdjustments = merged;
      })
      .then(reloadAudit)
      .catch((ex) => notify("Couldn't save adjustments \u2014 " + ex.message));
    notify(emp.name + "'s bonuses & deductions updated");
    setAdjEdit(null);
  };
  const openAdvBalance = advances.filter((v) => v.status === "Active").reduce((s, v) => s + (v.amount - v.repaid), 0);
  const payFiltered = employees.filter((e) => {
    const q = payQ.trim().toLowerCase();
    if (!q) return true;
    return (e.name + " " + e.dept + " " + statusFor(e)).toLowerCase().includes(q);
  });

  const filtered = employees.filter((e) => {
    const q = query.trim().toLowerCase();
    if (!q) return true;
    return (e.name + " " + e.role + " " + e.dept).toLowerCase().includes(q);
  });

  // Every employee gets a row, contract or not. Filtering the uncontracted out
  // hid exactly the people who need action — a new hire was invisible here and
  // had no route to a first contract. `c` is null for them and the row renders
  // an "add contract" state instead.
  const contracts = employees.map((e) => ({ emp: e, c: apiContracts[e.id] || null }));
  const expiring = contracts.filter((x) => x.c && x.c.daysLeft != null && x.c.daysLeft < 180).length;
  const missingContract = contractsLoaded ? contracts.filter((x) => !x.c).length : 0;
  const noContract = (empId) => contractsLoaded && !apiContracts[empId];

  return (
    <div className="page" data-screen-label={T("hr.hr")}>
      <KpiBar>
        <Stat label={T("hr.headcount")} value={String(headcount)} sub={TP("hr.departmentCount", deptCount)} />
        <Stat label={T("hr.monthlyPayroll")} value={money(payroll)} sub={T("hr.payrollRun", { period: curLabel })} />
        <Stat label={T("hr.onLeaveToday")} value={String(onLeave)} sub={onLeaveNames || T("hr.everyoneIn")} />
        <Stat label={T("ess.pendingRequests")} value={String(pending.length)} sub={T("hr.awaitingYourReview")} />
      </KpiBar>

      <Tabs active={tab} onChange={setTab} tabs={[
        { id: "people", label: T("hr.people"), count: headcount },
        { id: "contracts", label: T("crm.contracts"), count: expiring + missingContract },
        { id: "time", label: T("hr.timeLogs") },
        { id: "leave", label: T("hr.requests"), count: pending.length },
        { id: "payroll", label: T("hr.payrollTab") },
        { id: "devices", label: T("hrdev.tab") },
        { id: "audit", label: T("hr.auditLog"), count: auditRows.length },
        { id: "settings", label: T("hr.settingsTab") }
      ]} />

      {tab === "people" ? (
        <><div className="toolbar page-toolbar">
            <div className="searchbox">
              <Icon name="search" size={15} />
              <input placeholder={T("hr.searchEmployees")} value={query} onChange={(e) => setQuery(e.target.value)} />
            </div>
            {missingContract > 0 ? (
              <span className="dim" style={{ fontSize: 13 }}>{TP("hr.missingContractCount", missingContract)} · {T("hr.addFromProfile")}</span>
            ) : null}
            <Btn variant="primary" icon="plus" onClick={() => setShowAdd(true)}>{T("hr.addEmployee")}</Btn>
          </div>
        <section className="card">
          <DataTable rowKey={(r) => r.id} rows={filtered} onRow={(r) => setSel(r)}
            empty={query.trim() ? T("hr.noEmployeesMatchQuery", { q: query.trim() }) : T("hr.noEmployeesYet")} cols={[
            { k: "no", label: "#", w: "56px", render: (r) => <span className="mono dim">{r.no != null ? r.no : "—"}</span> },
            { k: "name", label: T("hr.employee"), render: (r) => (
                <span className="cellwho">
                  <Avatar name={r.name} size={28} />
                  {/* Hiring only asks for a name, so both of these can still be blank. */}
                  <span className="who2"><b>{r.name}</b><small>{r.role || "—"}</small></span>
                </span>
              ) },
            { k: "dept", label: T("hr.department"), w: "130px", render: (r) => <span className="dim">{r.dept || "—"}</span> },
            { k: "joined", label: T("hr.joined"), w: "90px", render: (r) => <span className="dim">{r.joined}</span> },
            // No contract means no pay to show — flag it here rather than printing a bare 0.
            // The badge is also the way out of that state: it opens the create form
            // straight from the list, while the row keeps opening the profile.
            { k: "salary", label: T("rec.salaryPerMo"), align: "right", w: "130px", render: (r) => (
                noContract(r.id) ? (
                  <span className="badge-act" title={T("hr.addContract")}
                    onClick={(e) => { e.stopPropagation(); setSelContract(r.id); }}>
                    <Badge tone="amber">{T("hr.noContract")}</Badge>
                  </span>
                )
                : <span className="mono strong">{money(r.salary)}</span>
              ) },
            { k: "status", label: T("common.status"), w: "110px", render: (r) => <Badge tone={r.status === "On leave" ? "amber" : undefined}>{r.status}</Badge> }
          ]} />
        </section></>
      ) : null}

      {tab === "contracts" ? (
        <><div className="toolbar page-toolbar">
            <span className="dim" style={{ fontSize: 13 }}>{
              missingContract > 0 ? TP("hr.missingContractCount", missingContract)
              : expiring > 0 ? TP("hr.renewalDueCount", expiring)
              : T("hr.allContractsHealthy")
            }</span>
          </div>
        <section className="card">
          <DataTable rowKey={(r) => r.emp.id} rows={contracts} onRow={(r) => setSelContract(r.emp.id)}
            empty={T("hr.noEmployeesYet")} cols={[
            { k: "no", label: T("hr.contractNo"), w: "72px", render: (r) => (
                <span className="dim mono">{r.c && r.c.no != null ? "#" + r.c.no : "—"}</span>
              ) },
            { k: "name", label: T("hr.employee"), render: (r) => (
                <span className="cellwho">
                  <Avatar name={r.emp.name} size={28} />
                  <span className="who2"><b>{r.emp.name}</b><small>{r.emp.role}</small></span>
                </span>
              ) },
            { k: "type", label: T("common.type"), w: "110px", render: (r) => (
                !r.c ? <Badge tone="amber">{T("hr.noContract")}</Badge>
                : <Badge tone={r.c.type === "Fixed-term" ? "blue" : undefined}>{TV(r.c.type)}</Badge>
              ) },
            { k: "start", label: T("ess.start"), w: "110px", render: (r) => <span className="dim mono">{r.c ? r.c.start : "\u2014"}</span> },
            { k: "end", label: T("ess.end"), w: "110px", render: (r) => <span className="dim mono">{(r.c && r.c.end) || "\u2014"}</span> },
            { k: "expiry", label: T("hr.expiry"), w: "120px", render: (r) => (
                !r.c ? <span className="dim">{"\u2014"}</span>
                : r.c.daysLeft == null ? <span className="dim">{T("hr.openEnded")}</span>
                : <Badge tone={r.c.daysLeft < 90 ? "red" : "amber"}>{r.c.daysLeft}d left</Badge>
              ) },
            { k: "probation", label: T("hr.probation"), w: "110px", render: (r) => (
                !r.c ? <span className="dim">{"\u2014"}</span>
                : r.c.probationPassed ? <span className="dim">{T("hr.passed")}</span> : <Badge tone="amber">{T("hr.inProgress")}</Badge>
              ) },
            { k: "hours", label: T("hr.hours"), align: "right", w: "70px", render: (r) => <span className="mono">{r.c ? <HoursHM h={r.c.hours} /> : "\u2014"}</span> },
            { k: "salary", label: T("rec.salaryPerMo"), align: "right", w: "110px", render: (r) => (
                !r.c ? <span className="dim">{"—"}</span>
                : <span className="mono strong">{money(r.emp.salary)}</span>
              ) }
          ]} />
        </section></>
      ) : null}

      {tab === "time" ? (() => {
        const rows = [];
        const sec = (x) => { const y = String(x).split(":"); return (+y[0] * 3600) + (+y[1] * 60) + (+y[2] || 0); };
        const hm = (x) => String(x).slice(0, 5); // late/early compare on HH:MM, not the seconds
        // api.js pre-renders `day` in English. Format from the ISO date instead —
        // dd/mm/yyyy reads the same in either language.
        const dayLabel = (iso, fallback) => dmy(iso) || fallback;
        // The date range picker is the only window now — with none set, every
        // punch on file shows, rather than a hidden last-N-days cutoff.
        apiTimeRows.forEach((t) => {
          if (timeFrom && t.dateIso < timeFrom) return;
          if (timeTo && t.dateIso > timeTo) return;
          const emp = employees.find((e) => e.id === t.empId) || { id: t.empId, name: t.empName, dept: t.empDept };
          if (timeDept !== ALL_DEPTS && (emp.dept || "") !== timeDept) return;
          rows.push({ key: t.id, emp, day: t.day, dateIso: t.dateIso, web: t.web, in: t.in, out: t.out,
            breakMin: t.breakMin, breakSec: t.breakSec,
            secs: t.out && t.in ? sec(t.out) - sec(t.in) - t.breakSec : null,
            late: !!t.in && hm(t.in) > "08:12", early: !!t.out && hm(t.out) < "16:30", fixed: t.fixed });
        });
        const PER = 10;
        const pages = Math.max(1, Math.ceil(rows.length / PER));
        const page = Math.min(timePage, pages);
        const slice = rows.slice((page - 1) * PER, page * PER);
        // Departments come from the people on file, so the list only ever offers
        // what a punch could actually belong to.
        const deptOptions = [ALL_DEPTS].concat(
          Array.from(new Set(employees.map((e) => e.dept).filter(Boolean))).sort()
        );
        return (
        <><div className="toolbar page-toolbar">
            <div className="toolbar-left" style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <span className="tl-range">
                <DateRangePicker from={timeFrom} to={timeTo} onChange={(f, t) => { setTimeFrom(f); setTimeTo(t); setTimePage(1); }} />
              </span>
              <div style={{ width: 190 }}>
                <SearchSelect options={deptOptions} value={timeDept} placeholder={T("hr.allDepartments")}
                  onChange={(v) => { setTimeDept(v || ALL_DEPTS); setTimePage(1); }} />
              </div>
            </div>
            <span className="dim" style={{ fontSize: 13 }}>{TP("hr.timeLogEntries", rows.length)} · {T("hr.punchSources")}</span>
          </div>
        <section className="card">
          <DataTable tableId="hr-timelogs" rowKey={(r) => r.key} rows={slice} onRow={(r) => setSel(r.emp)}
            empty={timeDept === ALL_DEPTS ? T("hr.noPunchesInWindow") : T("hr.noPunchesFromDept", { dept: TV(timeDept) })} cols={[
            { k: "emp", label: T("hr.employee"), render: (r) => (
                <span className="cellwho"><Avatar name={r.emp.name} size={26} /><span className="who2"><b>{r.emp.name}</b><small>{r.emp.dept}</small></span></span>
              ) },
            { k: "day", label: T("hr.day"), w: "140px", render: (r) => <span><span className="nw mono">{dayLabel(r.dateIso, r.day)}</span>{r.web ? <span className="ep-flag early">{T("ess.webFlag")}</span> : null}</span> },
            { k: "in", label: T("ess.checkIn"), w: "110px", render: (r) => <span className="mono"><TimeSec t={r.in} />{r.late ? <span className="ep-flag late">{T("ess.lateFlag")}</span> : null}</span> },
            { k: "out", label: T("ess.checkOut"), w: "110px", render: (r) => r.out ? <span className="mono"><TimeSec t={r.out} />{r.early ? <span className="ep-flag early">{T("ess.earlyFlag")}</span> : null}</span> : <span className="dim">—</span> },
            { k: "break", label: T("clock.break"), w: "100px", render: (r) => r.breakSec ? <span className="mono"><DurSec s={r.breakSec} /></span> : <span className="dim">—</span> },
            { k: "hours", label: T("hr.duration"), align: "right", w: "110px", render: (r) => r.secs == null ? <span className="dim">—</span> : <span className="mono strong"><DurSec s={r.secs} />{r.fixed ? <span className="ep-flag paid">{T("ess.fixedFlag")}</span> : null}</span> }
          ]} />
          <div className="pager">
            <span className="dim">{T("common.pagerRange", { from: (page - 1) * PER + 1, to: Math.min(page * PER, rows.length), total: rows.length })}</span>
            <span className="pager-btns">
              <button className="pager-btn" disabled={page <= 1} onClick={() => setTimePage(page - 1)}><Icon name="chevR" size={14} style={{ transform: "rotate(180deg)" }} /></button>
              {Array.from({ length: pages }, (_, i) => i + 1).map((n) => (
                <button key={n} className={"pager-btn" + (n === page ? " on" : "")} onClick={() => setTimePage(n)}>{n}</button>
              ))}
              <button className="pager-btn" disabled={page >= pages} onClick={() => setTimePage(page + 1)}><Icon name="chevR" size={14} /></button>
            </span>
          </div>
        </section></>
        );
      })() : null}

      {tab === "audit" ? (
        <><div className="toolbar page-toolbar">
            <span className="dim" style={{ fontSize: 13 }}>{T("hr.everyChangeMadeToEmployeeRecords")}</span>
          </div>
        <section className="card">
          <DataTable rowKey={(r) => r.id} rows={auditRows} empty={T("hr.noEditsRecorded")} cols={[
            { k: "time", label: T("hr.when"), w: "140px", render: (r) => <span className="dim mono">{r.time}</span> },
            { k: "by", label: T("hr.by"), w: "170px", render: (r) => (
                <span className="cellwho"><Avatar name={r.by} size={24} /><span>{r.by}</span></span>
              ) },
            { k: "emp", label: T("hr.employee"), w: "160px", render: (r) => <b>{r.emp}</b> },
            { k: "action", label: T("hr.action"), render: (r) => <span>{r.action}</span> },
            { k: "details", label: T("hr.details"), render: (r) => <span className="dim">{r.details || "\u2014"}</span> }
          ]} />
        </section></>
      ) : null}

      {/* Settings mounts only when opened: it loads the restriction, the branch
          list and the refusal log, none of which the other tabs need. */}
      {tab === "settings" ? <HrSettings /> : null}

      {/* Its own component: the device screen loads raw pushes and the unclaimed
          enrolment list, which nothing else on this page needs. */}
      {tab === "devices" ? <DeviceLogs /> : null}

      {tab === "leave" ? (() => {
        // Pending first: the rows that still need a decision stay at the top of the single list.
        const decided = leaves.filter((l) => l.status !== "Pending");
        const all = [...pending, ...decided];
        const rows = leaveView === "pending" ? pending : leaveView === "decided" ? decided : all;
        const empOf = (r) => employees.find((e) => e.id === r.empId);
        return (
        <><div className="toolbar page-toolbar">
            <Chips options={[
              { id: "all", label: T("hr.allRequests"), count: all.length },
              { id: "pending", label: T("hr.waitingForReview"), count: pending.length },
              { id: "decided", label: T("hr.decided"), count: decided.length }
            ]} active={leaveView} onChange={setLeaveView} />
          </div>
        <section className="card">
          <DataTable tableId="hr-leaves" rowKey={(r) => r.id} rows={rows}
            onRow={(r) => { const e = empOf(r); if (e) setSel(e); }}
            empty={leaveView === "pending" ? "All caught up — no pending requests." : "No leave requests yet."} cols={[
            { k: "emp", label: T("hr.employee"), render: (r) => (
                <span className="cellwho">
                  <Avatar name={r.emp} size={28} />
                  <span className="who2"><b>{r.emp}</b><small>{(empOf(r) || {}).dept || "—"}</small></span>
                </span>
              ) },
            { k: "type", label: T("common.type"), w: "110px", render: (r) => <Badge tone="blue">{r.type}</Badge> },
            { k: "from", label: T("ess.from"), w: "110px", render: (r) => <span className="dim mono">{r.from}</span> },
            { k: "to", label: T("ess.to"), w: "110px", render: (r) => <span className="dim mono">{r.to}</span> },
            { k: "days", label: T("ess.days"), align: "right", w: "70px", render: (r) => <span className="mono strong">{r.days}</span> },
            { k: "status", label: T("common.status"), w: "110px", render: (r) => <Badge>{r.status}</Badge> },
            { k: "note", label: T("hr.note"), render: (r) => r.note ? <span className="dim">“{r.note}”</span> : <span className="dim">—</span> },
            { k: "act", label: "", w: "230px", align: "right", render: (r) => (
                r.status !== "Pending" ? <span className="dim">—</span> : (
                  <span className="btnrow" style={{ justifyContent: "flex-end" }}>
                    <Btn small variant="primary" icon="check" onClick={(e) => { e.stopPropagation(); setDecideFor({ leave: r, action: "Approved" }); }}>{T("hr.approve")}</Btn>
                    <Btn small onClick={(e) => { e.stopPropagation(); setDecideFor({ leave: r, action: "Declined" }); }}>{T("hr.decline")}</Btn>
                    <Btn small icon="edit" onClick={(e) => { e.stopPropagation(); setEditReq(r); }}>{T("hr.edit")}</Btn>
                  </span>
                )
              ) }
          ]} />
        </section></>
        );
      })() : null}

      {tab === "payroll" ? (
        <div>
          <div className="toolbar" style={{ padding: "0 0 14px" }}>
            <Chips options={[
              { id: "payments", label: T("hr.salaryPayments"), count: employees.length - paidCount },
              { id: "adj", label: T("hr.bonusesDeductions"), count: adjustments.length },
              { id: "advances", label: T("hr.advances"), count: advances.filter((v) => v.status === "Active").length },
              { id: "run", label: T("hr.payrollRun", { period: curLabel.split(" ")[0] }) }
            ]} active={payView} onChange={setPayView} />
            {payView === "payments" || payView === "adj" ? (
              <div className="searchbox">
                <Icon name="search" size={15} />
                <input placeholder={T("hr.searchNameDepartmentStatus")} value={payQ} onChange={(e) => setPayQ(e.target.value)} />
              </div>
            ) : payView === "advances" ? (
              <Btn variant="primary" icon="plus" onClick={() => setShowAdvance(true)}>{T("hr.newAdvance")}</Btn>
            ) : null}
          </div>

          {payView === "payments" ? (
            <><div className="toolbar page-toolbar">
                <span className="dim" style={{ fontSize: 13 }}>{curLabel} · {T("hr.paidOfCount", { paid: paidCount, total: employees.length })} · {T("hr.openAdvanceBalance", { amount: money(openAdvBalance) })}</span>
                <span style={{ flex: 1 }} />
                <Btn variant="primary" icon="check" onClick={runPayroll}>{T("hr.runPayroll")}</Btn>
              </div>
            <section className="card">
              <DataTable rowKey={(r) => r.id} rows={payFiltered} onRow={(r) => setPayOpen(payOpen === r.id ? null : r.id)}
                expanded={(r) => {
                  if (payOpen !== r.id) return null;
                  const txs = txFor(r);
                  const rem = netFor(r) - paidAmt(r);
                  return (
                    <div className="paytx">
                      {txs.length === 0 ? <p className="dim" style={{ margin: "2px 0 8px", fontSize: 13 }}>{T("hr.noPaymentsRecordedYet")}</p> : (
                        <div className="ep-list" style={{ marginBottom: 8 }}>
                          <div className="ep-row ep-head ep-row-5"><span>{T("hr.ref")}</span><span>{T("common.date")}</span><span>{T("inst.method")}</span><span></span><span className="ta-r">{T("common.amount")}</span></div>
                          {txs.map((t, i) => (
                            <div className="ep-row ep-row-5" key={i}>
                              <span className="mono dim">{t.ref}</span>
                              <span className="mono">{t.date}</span>
                              <span>{t.method}</span>
                              <span></span>
                              <span className="ta-r mono strong">{money(t.amount)}</span>
                            </div>
                          ))}
                        </div>
                      )}
                      <div className="paytx-foot">
                        <span className="dim" style={{ fontSize: 12.5 }}>{money(paidAmt(r))} paid of {money(netFor(r))}{rem > 0 ? " \u00b7 " + money(rem) + " outstanding" : ""}</span>
                        {rem > 0 ? <Btn small icon="plus" onClick={(e) => { e.stopPropagation(); setRecordFor(r); }}>{T("hr.recordPayment")}</Btn> : null}
                      </div>
                    </div>
                  );
                }}
                empty={T("hr.noEmployeesMatchSearch")} cols={[
                { k: "name", label: T("hr.employee"), render: (r) => (
                    <span className="cellwho"><Avatar name={r.name} size={28} /><span className="who2"><b>{r.name}</b><small>{r.dept}</small></span></span>
                  ) },
                // A 0 base is nearly always a missing contract, not a zero salary — say which.
                { k: "base", label: T("hr.base"), align: "right", w: "115px", render: (r) => (
                    noContract(r.id) ? <Badge tone="amber">{T("hr.noContract")}</Badge>
                    : <span className="mono">{money(r.salary)}</span>
                  ) },
                { k: "adv", label: T("hr.advance"), align: "right", w: "100px", render: (r) => advFor(r) ? <span className="mono" style={{ color: "var(--red)" }}>−{money(advFor(r))}</span> : <span className="dim">—</span> },
                { k: "net", label: T("ess.netPay"), align: "right", w: "105px", render: (r) => <span className="mono strong">{money(netFor(r))}</span> },
                { k: "paid", label: T("hr.paid"), align: "right", w: "100px", render: (r) => paidAmt(r) ? <span className="mono" style={{ color: "var(--green)" }}>{money(paidAmt(r))}</span> : <span className="dim">—</span> },
                { k: "tx", label: T("hr.payments"), align: "right", w: "90px", render: (r) => txFor(r).length ? <span className="mono">{txFor(r).length}</span> : <span className="dim">—</span> },
                { k: "status", label: T("common.status"), w: "110px", render: (r) => {
                    const s = statusFor(r);
                    return <Badge tone={s === "Paid" ? "green" : s === "Partial" ? "blue" : s === "Processing" ? "blue" : "amber"}>{s}</Badge>;
                  } },
                { k: "paidon", label: T("ess.paidOn"), w: "100px", render: (r) => (
                    payDates[r.id] ? <span className="dim mono">{payDates[r.id]}</span> : <span className="dim">—</span>
                  ) },
                { k: "act", label: "", w: "150px", align: "right", render: (r) => (
                    <span className="btnrow" style={{ justifyContent: "flex-end" }}>
                      <Btn small icon="edit" onClick={(e) => { e.stopPropagation(); setPayEdit(r); }}>{T("hr.edit")}</Btn>
                      {statusFor(r) === "Paid" ? null : <Btn small onClick={(e) => { e.stopPropagation(); markPaid(r); }}>{T("hr.markPaid")}</Btn>}
                    </span>
                  ) }
              ]} />
            </section></>
          ) : null}

          {payView === "adj" ? (
            <><div className="toolbar page-toolbar">
                <span className="dim" style={{ fontSize: 13 }}>{T("hr.perEmployeeBonusesDeductions")} · {curLabel}</span>
                <span style={{ flex: 1 }} />
                <Btn variant="primary" icon="plus" onClick={() => setShowAdjust(true)}>{T("hr.addBonusDeduction")}</Btn>
              </div>
            <section className="card">
              <DataTable rowKey={(r) => r.id} rows={payFiltered} onRow={(r) => setAdjEdit(r)}
                empty={T("hr.noEmployeesMatchSearch")} cols={[
                { k: "name", label: T("hr.employee"), render: (r) => (
                    <span className="cellwho"><Avatar name={r.name} size={28} /><span className="who2"><b>{r.name}</b><small>{r.dept}</small></span></span>
                  ) },
                { k: "bonus", label: T("hr.bonuses"), align: "right", w: "110px", render: (r) => bonusFor(r.id) ? <span className="mono" style={{ color: "var(--green)" }}>+{money(bonusFor(r.id))}</span> : <span className="dim">—</span> },
                { k: "ded", label: T("hr.deductions"), align: "right", w: "110px", render: (r) => dedFor(r.id) ? <span className="mono" style={{ color: "var(--red)" }}>−{money(dedFor(r.id))}</span> : <span className="dim">—</span> },
                { k: "items", label: T("hr.entries"), align: "right", w: "80px", render: (r) => {
                    const n = adjustments.filter((a) => a.empId === r.id).length;
                    return n ? <span className="mono">{n}</span> : <span className="dim">—</span>;
                  } },
                { k: "act", label: "", w: "80px", align: "right", render: (r) => (
                    <Btn small icon="edit" onClick={(e) => { e.stopPropagation(); setAdjEdit(r); }}>{T("hr.edit")}</Btn>
                  ) }
              ]} />
            </section></>
          ) : null}

          {payView === "advances" ? (
            <><div className="toolbar page-toolbar">
                <span className="dim" style={{ fontSize: 13 }}>{T("hr.salaryAdvancesRepaidThroughMonthlyPayroll")}</span>
              </div>
            <section className="card">
              <DataTable rowKey={(r) => r.id} rows={advances} empty={T("hr.noAdvancesIssued")} cols={[
                { k: "id", label: T("hr.ref"), w: "80px", render: (r) => <span className="mono strong">{refNo(r.id)}</span> },
                { k: "emp", label: T("hr.employee"), render: (r) => (
                    <span className="cellwho"><Avatar name={r.emp} size={26} /><span className="who2"><b>{r.emp}</b><small>{r.note || "—"}</small></span></span>
                  ) },
                { k: "issued", label: T("hr.issued"), w: "110px", render: (r) => <span className="dim mono">{r.issued}</span> },
                { k: "amount", label: T("common.amount"), align: "right", w: "90px", render: (r) => <span className="mono">{money(r.amount)}</span> },
                { k: "monthly", label: T("hr.monthly"), align: "right", w: "85px", render: (r) => <span className="mono dim">{money(r.monthly)}</span> },
                { k: "repaid", label: T("hr.repaid"), align: "right", w: "85px", render: (r) => <span className="mono" style={{ color: "var(--green)" }}>{money(r.repaid)}</span> },
                { k: "balance", label: T("hr.balance"), align: "right", w: "90px", render: (r) => <span className="mono strong">{money(r.amount - r.repaid)}</span> },
                { k: "status", label: T("common.status"), w: "90px", render: (r) => <Badge tone={r.status === "Active" ? "amber" : "green"}>{TV(r.status)}</Badge> },
                { k: "act", label: "", w: "135px", align: "right", render: (r) => (
                    r.status === "Active" ? <Btn small onClick={(e) => { e.stopPropagation(); setRepayAdv(r); }}>{T("hr.recordRepayment")}</Btn> : null
                  ) }
              ]} />
            </section></>
          ) : null}

          {payView === "run" ? (
        <div className="grid-23 side-table">
          <section className="card pad">
            <SectionHead title={T("hr.payrollRun", { period: curLabel })} action={
              <Badge tone={computedCount === 0 ? undefined : paidCount >= headcount && headcount ? "green" : "amber"}>
                {computedCount === 0 ? T("hr.runNotRun") : paidCount >= headcount && headcount ? T("hr.runComplete") : T("hr.runInProgress")}
              </Badge>} />
            <div className="amount-hero" style={{ marginTop: 4 }}>
              <span className="display">{money(payroll)}</span>
              <span className="dim">{T("hr.employeesCount", { n: headcount })}</span>
            </div>
            <Progress value={headcount ? Math.round((paidCount / headcount) * 100) : 0} />
            <ul className="steps">
              <li className={computedCount ? "done" : ""}>
                <Icon name={computedCount ? "check" : "dot"} size={14} stroke={2.4} />
                {computedCount ? T("hr.recordsComputed", { n: computedCount, total: headcount }) : T("hr.payrollNotRunYet")}
              </li>
              <li className={paidCount ? (paidCount >= headcount ? "done" : "now") : ""}>
                <Icon name={paidCount >= headcount && headcount ? "check" : "clock"} size={14} stroke={2.4} />
                {T("hr.paidOfCount", { paid: paidCount, total: headcount })}
              </li>
              <li className={openAdvBalance ? "now" : "done"}>
                <Icon name={openAdvBalance ? "clock" : "check"} size={14} stroke={2.4} />
                {openAdvBalance ? T("hr.openAdvanceBalance", { amount: money(openAdvBalance) }) : T("hr.noOpenAdvances")}
              </li>
            </ul>
            <Btn variant="primary" icon="check" onClick={runPayroll}>{computedCount ? T("hr.reRunPayroll") : T("hr.runPayroll")}</Btn>
            <div className="formsec">{T("hr.bonusesDeductions")}</div>
            {adjustments.length === 0 ? <p className="dim" style={{ margin: "0 0 10px", fontSize: 13 }}>{T("hr.noAdjustmentsThisRun")}</p> : (
              <ul className="leave-list" style={{ marginBottom: 10 }}>
                {adjustments.map((a, i) => (
                  <li key={i} className="leave-row">
                    <span className="leave-main">
                      <b>{a.emp}</b>
                      <small>{a.kind}{a.note ? " \u00b7 " + a.note : ""}</small>
                    </span>
                    <span className="mono strong" style={{ color: a.kind === "Deduction" ? "var(--red)" : "var(--green)" }}>
                      {a.kind === "Deduction" ? "\u2212" : "+"}{money(a.amount)}
                    </span>
                  </li>
                ))}
              </ul>
            )}
            <Btn small icon="plus" onClick={() => setShowAdjust(true)}>{T("hr.addBonusDeduction")}</Btn>
          </section>
          <section className="card">
            <div className="pad" style={{ paddingBottom: 0 }}><SectionHead title={T("hr.pastRuns")} /></div>
            {/* Narrow side column: dense padding and column widths kept at
                min-content so "2026-08" stays on one line instead of wrapping. */}
            <DataTable dense rowKey={(r) => r.period} rows={payHistory} cols={[
              { k: "period", label: T("hr.period"), render: (r) => <b className="nw">{r.period}</b> },
              { k: "employees", label: T("hr.people"), w: "56px", align: "right", render: (r) => <span className="mono">{r.employees}</span> },
              { k: "total", label: T("common.total"), align: "right", w: "84px", render: (r) => <span className="mono strong">{money(r.total)}</span> },
              { k: "status", label: T("common.status"), w: "72px", render: (r) => <Badge>{TV(r.status)}</Badge> }
            ]} />
          </section>
        </div>
          ) : null}
        </div>
      ) : null}

      {/* Contracts tab opens the contract itself, not the whole employee record. */}
      <HrContractModal row={contracts.find((x) => x.emp.id === selContract) || null}
        onClose={() => setSelContract(null)}
        onChanged={() => {
          reloadContracts();
          API.employees.listAll().then((list) => { setEmployees(list); D.employees = list; }).catch(() => {});
          reloadAudit();
        }} />

      <EmployeeProfile emp={sel} contract={sel ? apiContracts[sel.id] : null}
        onClose={() => setSel(null)} onUpdate={updateEmployee}
        onContractsChanged={() => {
          reloadContracts();
          API.employees.listAll().then((list) => { setEmployees(list); D.employees = list; }).catch(() => {});
        }} />

      <AddEmployeeModal open={showAdd} existing={employees} onClose={closeAdd} onAdd={addEmployee}
        onPickListRenamed={() => {
          API.employees.listAll().then((list) => { setEmployees(list); D.employees = list; }).catch(() => {});
        }} />

      <RequestEditModal open={!!editReq} req={editReq} onClose={() => setEditReq(null)}
        onSave={(r) => { setLeaves((ls) => ls.map((l) => (l.id === r.id ? r : l))); setEditReq(null); notify("Request updated");
          API.leaves.update(r.id, r).then(reloadAudit).catch((ex) => notify("Couldn't save request \u2014 " + ex.message)); }} />

      <DecideModal open={!!decideFor} info={decideFor} onClose={() => setDecideFor(null)}
        onConfirm={(note) => { decide(decideFor.leave.id, decideFor.action, note); setDecideFor(null); }} />

      <PayAdjustModal open={showAdjust} employees={employees} onClose={() => setShowAdjust(false)} onSave={addAdjustment} />

      <AdvanceModal open={showAdvance} employees={employees} onClose={() => setShowAdvance(false)} onSave={issueAdvance} />

      <RepayModal open={!!repayAdv} advance={repayAdv} onClose={() => setRepayAdv(null)} onSave={recordRepay} />

      <RecordPayModal open={!!recordFor} emp={recordFor} remaining={recordFor ? netFor(recordFor) - paidAmt(recordFor) : 0}
        onClose={() => setRecordFor(null)} onSave={({ amount, method }) => { addTx(recordFor, amount, method); setRecordFor(null); }} />

      <PayEditModal open={!!payEdit} emp={payEdit} status={payEdit ? statusFor(payEdit) : "Pending"} paidOn={payEdit ? payDates[payEdit.id] : ""}
        onClose={() => setPayEdit(null)} onSave={applyPayEdit} />

      <EmpAdjustModal open={!!adjEdit} emp={adjEdit} adjustments={adjustments}
        onClose={() => setAdjEdit(null)} onSave={applyAdjust} />
    </div>
  );
}
window.HR = HR;

