/* ==========================================================================
   Monitoring Perawatan LV & Bus — versi server (PHP + MySQL)
   Dijalankan langsung di browser: React + Babel dimuat dari CDN,
   data disimpan di MySQL lewat api/index.php
   ========================================================================== */

const { useState, useEffect, useMemo } = React;

const API = "api/index.php";

async function api(action, payload) {
  const res = await fetch(API + "?a=" + action, {
    method: payload ? "POST" : "GET",
    headers: { "Content-Type": "application/json" },
    credentials: "same-origin",
    body: payload ? JSON.stringify(payload) : undefined,
  });
  if (res.status === 401) throw new Error("401");
  let j;
  try {
    j = await res.json();
  } catch (e) {
    throw new Error("Balasan server tidak terbaca. Periksa pengaturan PHP di hosting.");
  }
  if (!j.ok) throw new Error(j.error || "Server menolak permintaan");
  return j.data;
}

/* ============================== STORAGE ============================== */

const KEY = "fleet-lv-bus:v1";

const emptyData = {
  units: [],
  services: [],
  parts: [],
  incomes: [],
  loans: [],
  karyawan: [],
  merek: [],
  asuransi: [],
};

const seed = () => {
  const today = new Date();
  const d = (minus) => {
    const x = new Date(today);
    x.setDate(x.getDate() - minus);
    return x.toISOString().slice(0, 10);
  };
  const units = [
    { id: "u1", kode: "LV-01", noLambung: "L-101", tipe: "LV", merek: "Toyota", model: "Hilux D-Cab", nopol: "B 9012 KHD", tahun: 2022, odometer: 86400, asuransiId: "a1", aktif: true },
    { id: "u2", kode: "LV-02", noLambung: "L-102", tipe: "LV", merek: "Mitsubishi", model: "Triton", nopol: "B 1874 RFA", tahun: 2021, odometer: 124800, asuransiId: "a2", aktif: true },
    { id: "u3", kode: "BUS-01", noLambung: "B-201", tipe: "Bus", merek: "Hino", model: "RK8 / 45 seat", nopol: "B 7233 TGA", tahun: 2020, odometer: 312500, asuransiId: "a3", aktif: true },
    { id: "u4", kode: "BUS-02", noLambung: "B-202", tipe: "Bus", merek: "Mercedes-Benz", model: "OH 1526 / 40 seat", nopol: "B 7418 TGB", tahun: 2023, odometer: 98200, asuransiId: "a4", aktif: true },
  ];
  const services = [
    { id: "s1", unitId: "u3", tanggal: d(6), jenis: "Servis rutin", odometer: 310000, deskripsi: "Ganti oli mesin + filter", biaya: 2850000, vendor: "Bengkel Sinar Jaya" },
    { id: "s2", unitId: "u1", tanggal: d(12), jenis: "Ban", odometer: 85200, deskripsi: "Rotasi 4 ban + spooring", biaya: 750000, vendor: "Ban Mandiri" },
    { id: "s3", unitId: "u2", tanggal: d(23), jenis: "Perbaikan", odometer: 122100, deskripsi: "Ganti kampas rem depan", biaya: 1650000, vendor: "Workshop internal" },
    { id: "s4", unitId: "u4", tanggal: d(40), jenis: "Servis rutin", odometer: 92000, deskripsi: "Servis 90.000 km", biaya: 4200000, vendor: "Dealer resmi" },
    { id: "s5", unitId: "u3", tanggal: d(58), jenis: "Perbaikan", odometer: 298400, deskripsi: "Overhaul kompresor AC", biaya: 7300000, vendor: "Bengkel Sinar Jaya" },
  ];
  const parts = [
    { id: "p1", unitId: "u3", nama: "Oli mesin", kmPasang: 310000, lifetimeKm: 10000, tglPasang: d(6), lifetimeBulan: 3, harga: 1900000 },
    { id: "p2", unitId: "u3", nama: "Ban depan (2 pcs)", kmPasang: 265000, lifetimeKm: 60000, tglPasang: d(210), lifetimeBulan: 24, harga: 8600000 },
    { id: "p3", unitId: "u1", nama: "Kampas rem depan", kmPasang: 62000, lifetimeKm: 30000, tglPasang: d(320), lifetimeBulan: 24, harga: 1250000 },
    { id: "p4", unitId: "u2", nama: "Oli mesin", kmPasang: 118000, lifetimeKm: 8000, tglPasang: d(75), lifetimeBulan: 4, harga: 620000 },
    { id: "p5", unitId: "u4", nama: "Filter solar", kmPasang: 92000, lifetimeKm: 20000, tglPasang: d(40), lifetimeBulan: 12, harga: 480000 },
    { id: "p6", unitId: "u4", nama: "Aki 2x 100Ah", kmPasang: 60000, lifetimeKm: 120000, tglPasang: d(430), lifetimeBulan: 18, harga: 3400000 },
  ];
  const incomes = [
    { id: "i1", unitId: "u3", tanggal: d(5), sumber: "Sewa harian", jumlah: 18500000, keterangan: "Trip Jakarta-Bandung 3 hari" },
    { id: "i2", unitId: "u4", tanggal: d(11), sumber: "Kontrak", jumlah: 42000000, keterangan: "Antar-jemput karyawan (bulanan)" },
    { id: "i3", unitId: "u1", tanggal: d(19), sumber: "Sewa harian", jumlah: 6200000, keterangan: "Support survei site" },
    { id: "i4", unitId: "u3", tanggal: d(38), sumber: "Sewa harian", jumlah: 21000000, keterangan: "Wisata rombongan" },
    { id: "i5", unitId: "u4", tanggal: d(42), sumber: "Kontrak", jumlah: 42000000, keterangan: "Antar-jemput karyawan (bulanan)" },
    { id: "i6", unitId: "u2", tanggal: d(50), sumber: "Sewa bulanan", jumlah: 9500000, keterangan: "Unit operasional proyek" },
  ];
  const startMonth = (mBack) => {
    const x = new Date(today);
    x.setMonth(x.getMonth() - mBack);
    return `${x.getFullYear()}-${String(x.getMonth() + 1).padStart(2, "0")}`;
  };
  const loans = [
    {
      id: "l1",
      kode: "KRD-2023-014",
      unitId: "u4",
      lembaga: "BCA Finance",
      pokok: 780000000,
      angsuran: 14900000,
      tenor: 60,
      mulai: startMonth(14),
      tglJatuhTempo: 10,
      pembayaran: Array.from({ length: 13 }, (_, i) => ({ ke: i + 1, tanggal: d(400 - i * 30), jumlah: 14900000 })),
    },
    {
      id: "l2",
      kode: "KRD-2022-007",
      unitId: "u2",
      lembaga: "Mandiri Tunas Finance",
      pokok: 310000000,
      angsuran: 7250000,
      tenor: 48,
      mulai: startMonth(26),
      tglJatuhTempo: 25,
      pembayaran: Array.from({ length: 26 }, (_, i) => ({ ke: i + 1, tanggal: d(780 - i * 30), jumlah: 7250000 })),
    },
  ];
  const dp = (plus) => {
    const x = new Date(today);
    x.setDate(x.getDate() + plus);
    return x.toISOString().slice(0, 10);
  };
  const karyawan = [
    { id: "k1", nrp: "NRP-0012", nama: "Sanusi", jabatan: "Sopir", statusKerja: "Tetap", unitId: "u1", hp: "0812-3344-5566", jenisSIM: "SIM B1 Umum", simExp: dp(420), tglMasuk: d(1500), gajiPokok: 4800000, tunjangan: 1200000, aktif: true },
    { id: "k2", nrp: "NRP-0027", nama: "Deni Prasetyo", jabatan: "Sopir", statusKerja: "Kontrak", unitId: "u2", hp: "0813-7788-1020", jenisSIM: "SIM B1 Umum", simExp: dp(18), tglMasuk: d(700), gajiPokok: 4500000, tunjangan: 1000000, aktif: true },
    { id: "k3", nrp: "NRP-0004", nama: "Warsito", jabatan: "Sopir", statusKerja: "Tetap", unitId: "u3", hp: "0857-2211-9080", jenisSIM: "SIM B2 Umum", simExp: dp(-12), tglMasuk: d(2400), gajiPokok: 6200000, tunjangan: 1800000, aktif: true },
    { id: "k4", nrp: "NRP-0019", nama: "Iwan Setiawan", jabatan: "Sopir", statusKerja: "Tetap", unitId: "u4", hp: "0878-4455-2311", jenisSIM: "SIM B2 Umum", simExp: dp(260), tglMasuk: d(980), gajiPokok: 6000000, tunjangan: 1800000, aktif: true },
    { id: "k5", nrp: "NRP-0041", nama: "Rudi Hartono", jabatan: "Kernet", statusKerja: "Harian", unitId: "u3", hp: "0821-9090-3344", jenisSIM: "", simExp: "", tglMasuk: d(300), gajiPokok: 2600000, tunjangan: 600000, aktif: true },
    { id: "k6", nrp: "NRP-0008", nama: "Bambang S.", jabatan: "Mekanik", statusKerja: "Tetap", unitId: "", hp: "0811-2323-7788", jenisSIM: "SIM A", simExp: dp(95), tglMasuk: d(1900), gajiPokok: 5400000, tunjangan: 900000, aktif: true },
    { id: "k7", nrp: "NRP-0033", nama: "Lestari", jabatan: "Admin", statusKerja: "Tetap", unitId: "", hp: "0819-6767-1212", jenisSIM: "", simExp: "", tglMasuk: d(1100), gajiPokok: 4200000, tunjangan: 700000, aktif: true },
  ];
  const merek = [
    { id: "m1", nama: "Toyota", tipe: "LV", agen: "Auto2000", keterangan: "Hilux, Innova" },
    { id: "m2", nama: "Mitsubishi", tipe: "LV", agen: "Dipo Star Finance", keterangan: "Triton, Pajero" },
    { id: "m3", nama: "Hino", tipe: "Bus", agen: "Hino Sales Indonesia", keterangan: "RK8, RN285" },
    { id: "m4", nama: "Mercedes-Benz", tipe: "Bus", agen: "Daimler Commercial", keterangan: "OH 1526, OH 1626" },
    { id: "m5", nama: "Isuzu", tipe: "Umum", agen: "Isuzu Astra", keterangan: "Elf, Traga" },
  ];
  const asuransi = [
    { id: "a1", kode: "ASR-001", penanggung: "Garda Oto", jenis: "All Risk", nomorPolis: "GO-2405-88121", premi: 9800000, mulai: d(160), berakhir: dp(205), keterangan: "" },
    { id: "a2", kode: "ASR-002", penanggung: "Asuransi Sinar Mas", jenis: "TLO", nomorPolis: "SM-2312-40917", premi: 4300000, mulai: d(250), berakhir: dp(115), keterangan: "" },
    { id: "a3", kode: "ASR-003", penanggung: "Adira Insurance", jenis: "All Risk + Rider", nomorPolis: "AD-2308-77304", premi: 21500000, mulai: d(343), berakhir: dp(22), keterangan: "Termasuk tanggung jawab pihak ketiga" },
    { id: "a4", kode: "ASR-004", penanggung: "Zurich Asuransi", jenis: "All Risk", nomorPolis: "ZR-2401-19286", premi: 24800000, mulai: d(120), berakhir: dp(245), keterangan: "" },
  ];
  return { units, services, parts, incomes, loans, karyawan, merek, asuransi };
};

/* ============================== HELPERS ============================== */

const uid = () => Math.random().toString(36).slice(2, 9);
const rp = (n) => "Rp " + Math.round(Number(n) || 0).toLocaleString("id-ID");
const rpShort = (n) => {
  const v = Number(n) || 0;
  if (Math.abs(v) >= 1e9) return (v / 1e9).toFixed(1).replace(".", ",") + " M";
  if (Math.abs(v) >= 1e6) return (v / 1e6).toFixed(0) + " jt";
  if (Math.abs(v) >= 1e3) return (v / 1e3).toFixed(0) + " rb";
  return String(v);
};
const km = (n) => (Number(n) || 0).toLocaleString("id-ID") + " km";
const todayISO = () => new Date().toISOString().slice(0, 10);
const fmtDate = (iso) => {
  if (!iso) return "—";
  const dt = new Date(iso + "T00:00:00");
  if (isNaN(dt)) return iso;
  return dt.toLocaleDateString("id-ID", { day: "2-digit", month: "short", year: "numeric" });
};
const monthKey = (iso) => (iso || "").slice(0, 7);
const monthLabel = (key) => {
  const [y, m] = key.split("-");
  const nm = ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"];
  return `${nm[Number(m) - 1]} ${String(y).slice(2)}`;
};
const daysBetween = (a, b) => Math.round((new Date(b) - new Date(a)) / 86400000);
const addMonths = (ym, n) => {
  const [y, m] = ym.split("-").map(Number);
  const dt = new Date(y, m - 1 + n, 1);
  return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}`;
};

/* Status umur part: gabungan pemakaian km dan umur waktu, ambil yang paling mendesak */
function partStatus(part, unit) {
  const odo = unit ? unit.odometer : part.kmPasang;
  const pakaiKm = Math.max(0, odo - part.kmPasang);
  const pctKm = part.lifetimeKm > 0 ? pakaiKm / part.lifetimeKm : 0;
  const sisaKm = part.lifetimeKm > 0 ? part.kmPasang + part.lifetimeKm - odo : null;

  let pctWaktu = 0;
  let sisaHari = null;
  if (part.lifetimeBulan > 0 && part.tglPasang) {
    const umurHari = daysBetween(part.tglPasang, todayISO());
    const totalHari = part.lifetimeBulan * 30.4;
    pctWaktu = umurHari / totalHari;
    sisaHari = Math.round(totalHari - umurHari);
  }
  const pct = Math.max(pctKm, pctWaktu);
  const level = pct >= 1 ? "lewat" : pct >= 0.85 ? "segera" : "aman";
  const basis = pctWaktu > pctKm ? "waktu" : "jarak";
  return { pct, pctKm, pctWaktu, sisaKm, sisaHari, level, basis, pakaiKm };
}

function loanStatus(loan) {
  const paid = loan.pembayaran ? loan.pembayaran.length : 0;
  const sisaTenor = Math.max(0, loan.tenor - paid);
  const sisaBayar = sisaTenor * loan.angsuran;
  const totalDibayar = (loan.pembayaran || []).reduce((s, p) => s + (Number(p.jumlah) || 0), 0);
  const nextMonth = addMonths(loan.mulai, paid);
  const [y, m] = nextMonth.split("-").map(Number);
  const dueDate = new Date(y, m - 1, Math.min(loan.tglJatuhTempo || 1, 28));
  const dueISO = dueDate.toISOString().slice(0, 10);
  const selisih = daysBetween(todayISO(), dueISO);
  const lunas = sisaTenor === 0;
  const level = lunas ? "lunas" : selisih < 0 ? "telat" : selisih <= 7 ? "segera" : "aman";
  return { paid, sisaTenor, sisaBayar, totalDibayar, dueISO, selisih, lunas, level };
}

/* Masa berlaku SIM: peringatan 30 hari sebelum habis */
function simStatus(k) {
  if (!k.simExp) return null;
  const sisaHari = daysBetween(todayISO(), k.simExp);
  const level = sisaHari < 0 ? "lewat" : sisaHari <= 30 ? "segera" : "aman";
  return { sisaHari, level };
}

const totalGaji = (k) => (Number(k.gajiPokok) || 0) + (Number(k.tunjangan) || 0);

/* Masa berlaku polis asuransi: peringatan 30 hari sebelum berakhir */
function asuransiStatus(a) {
  if (!a || !a.berakhir) return null;
  const sisaHari = daysBetween(todayISO(), a.berakhir);
  const level = sisaHari < 0 ? "lewat" : sisaHari <= 30 ? "segera" : "aman";
  return { sisaHari, level };
}

/* Impor massal: satu baris per orang, dipisah | atau tab atau koma */
const toNum = (v) => Number(String(v || "").replace(/[^\d]/g, "")) || 0;
function normDate(s) {
  if (!s) return "";
  const t = s.trim();
  const m = t.match(/^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{4})$/);
  if (m) return `${m[3]}-${m[2].padStart(2, "0")}-${m[1].padStart(2, "0")}`;
  return /^\d{4}-\d{2}-\d{2}$/.test(t) ? t : "";
}
function parseKaryawan(text, units) {
  const baris = String(text || "")
    .split(/\r?\n/)
    .map((l) => l.trim())
    .filter(Boolean);
  const hasil = [];
  const gagal = [];
  baris.forEach((line) => {
    if (/^(nrp|nama)\s*[|,\t]/i.test(line)) return;
    const parts = /[|\t]/.test(line) ? line.split(/[|\t]/) : line.split(",");
    const g = (i) => (parts[i] || "").trim();
    const nama = g(1);
    if (!nama) {
      gagal.push(line);
      return;
    }
    const kode = g(3).toUpperCase();
    const u = units.find((x) => (x.kode || "").toUpperCase() === kode);
    hasil.push({
      id: uid(),
      nrp: g(0),
      nama,
      jabatan: g(2) || "Sopir",
      unitId: u ? u.id : "",
      statusKerja: g(4) || "Tetap",
      hp: g(5),
      jenisSIM: g(6),
      simExp: normDate(g(7)),
      tglMasuk: todayISO(),
      gajiPokok: toNum(g(8)),
      tunjangan: toNum(g(9)),
      aktif: true,
    });
  });
  return { hasil, gagal };
}

/* ============================== UI ATOMS ============================== */

const Sheet = ({ open, title, onClose, children, footer }) => {
  if (!open) return null;
  return (
    <div className="sheet-scrim" onClick={onClose}>
      <div className="sheet" onClick={(e) => e.stopPropagation()}>
        <div className="sheet-head">
          <h3>{title}</h3>
          <button className="icon-btn" onClick={onClose} aria-label="Tutup">
            ✕
          </button>
        </div>
        <div className="sheet-body">{children}</div>
        {footer ? <div className="sheet-foot">{footer}</div> : null}
      </div>
    </div>
  );
};

const Field = ({ label, hint, children }) => (
  <label className="field">
    <span className="field-label">
      {label}
      {hint ? <em>{hint}</em> : null}
    </span>
    {children}
  </label>
);

const Plate = ({ children }) => <span className="plate">{children}</span>;

const Tag = ({ tone = "netral", children }) => <span className={`tag tag-${tone}`}>{children}</span>;

const Empty = ({ title, action, onAction }) => (
  <div className="empty">
    <p>{title}</p>
    {action ? (
      <button className="btn btn-primary" onClick={onAction}>
        {action}
      </button>
    ) : null}
  </div>
);

const Wear = ({ pct, level }) => (
  <div className="wear" role="img" aria-label={`Pemakaian ${Math.round(pct * 100)} persen`}>
    <div className={`wear-fill wear-${level}`} style={{ width: `${Math.min(100, pct * 100)}%` }} />
    <div className="wear-ticks" />
  </div>
);

/* ============================== APP ============================== */

const NAMA_HAK = {
  ringkas: "Ringkas (dasbor)",
  unit: "Unit",
  servis: "Servis",
  part: "Part",
  keuangan: "Keuangan",
  master: "Menu Master",
  m_unit: "Master · Unit",
  m_karyawan: "Master · Karyawan",
  m_merek: "Master · Merek",
  m_asuransi: "Master · Asuransi",
  m_pinjaman: "Master · Pinjaman",
  aktivitas: "Master · Catatan aktivitas",
  tambah: "Tambah & ubah data",
  hapus: "Hapus data",
};

const SANDI_BAWAAN = { admin: "3333", staff: "2222", superadmin: "" };

const boleh = (user, k) => !!user && (user.peran === "superadmin" || (user.hak || []).includes(k));

function FleetMonitor({ user, onLogout }) {
  const [data, setData] = useState(emptyData);
  const [loading, setLoading] = useState(true);
  const [tab, setTab] = useState("dash");
  const [sheet, setSheet] = useState(null); // {kind, payload}
  const [form, setForm] = useState({});
  const [filterTipe, setFilterTipe] = useState("Semua");
  const [subMaster, setSubMaster] = useState("unit");
  const [saving, setSaving] = useState(false);
  const [err, setErr] = useState("");

  useEffect(() => {
    (async () => {
      try {
        const d = await api("load");
        setData({ ...emptyData, ...d });
      } catch (e) {
        if (e.message === "401") {
          onLogout();
          return;
        }
        setErr(e.message);
      }
      setLoading(false);
    })();
  }, []);

  const persist = async (next) => {
    const sebelum = data;
    setData(next);
    setSaving(true);
    try {
      await api("sync", next);
      setErr("");
    } catch (e) {
      setData(sebelum);
      setErr(e.message === "401" ? "Sesi berakhir. Masuk lagi untuk menyimpan." : "Perubahan gagal disimpan: " + e.message);
    }
    setSaving(false);
  };

  const unitById = useMemo(() => {
    const m = {};
    data.units.forEach((u) => (m[u.id] = u));
    return m;
  }, [data.units]);

  const visibleUnits = useMemo(
    () => data.units.filter((u) => filterTipe === "Semua" || u.tipe === filterTipe),
    [data.units, filterTipe]
  );

  const openSheet = (kind, payload = {}) => {
    setForm(payload.form || {});
    setSheet({ kind, payload });
  };
  const closeSheet = () => setSheet(null);
  const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.value }));
  const num = (v) => Number(String(v ?? "").replace(/[^\d-]/g, "")) || 0;

  /* ---------- aksi data ---------- */

  const saveUnit = () => {
    if (!form.kode) return;
    const rec = {
      id: form.id || uid(),
      kode: form.kode.trim(),
      noLambung: (form.noLambung || "").trim(),
      tipe: form.tipe || "LV",
      merek: form.merek || "",
      model: form.model || "",
      nopol: (form.nopol || "").toUpperCase(),
      tahun: num(form.tahun),
      odometer: num(form.odometer),
      asuransiId: form.asuransiId || "",
      aktif: true,
    };
    const units = form.id ? data.units.map((u) => (u.id === form.id ? rec : u)) : [...data.units, rec];
    // kode pinjaman: satu unit satu kontrak, lepaskan dari unit lain
    const loans = data.loans.map((l) => {
      if (l.id === form.loanId) return { ...l, unitId: rec.id };
      if (l.unitId === rec.id && l.id !== form.loanId) return { ...l, unitId: "" };
      return l;
    });
    persist({ ...data, units, loans });
    closeSheet();
  };

  const saveMerek = () => {
    if (!form.nama) return;
    const rec = {
      id: form.id || uid(),
      nama: form.nama.trim(),
      tipe: form.tipe || "Umum",
      agen: form.agen || "",
      keterangan: form.keterangan || "",
    };
    const list = data.merek || [];
    const merek = form.id ? list.map((m) => (m.id === rec.id ? rec : m)) : [...list, rec];
    persist({ ...data, merek });
    closeSheet();
  };

  const saveAsuransi = () => {
    if (!form.penanggung && !form.kode) return;
    const rec = {
      id: form.id || uid(),
      kode: (form.kode || "").trim(),
      penanggung: form.penanggung || "",
      jenis: form.jenis || "All Risk",
      nomorPolis: form.nomorPolis || "",
      premi: num(form.premi),
      mulai: form.mulai || todayISO(),
      berakhir: form.berakhir || "",
      keterangan: form.keterangan || "",
    };
    const list = data.asuransi || [];
    const asuransi = form.id ? list.map((a) => (a.id === rec.id ? rec : a)) : [...list, rec];
    persist({ ...data, asuransi });
    closeSheet();
  };

  const saveService = () => {
    if (!form.unitId) return;
    const rec = {
      id: form.id || uid(),
      unitId: form.unitId,
      tanggal: form.tanggal || todayISO(),
      jenis: form.jenis || "Servis rutin",
      odometer: num(form.odometer),
      deskripsi: form.deskripsi || "",
      biaya: num(form.biaya),
      vendor: form.vendor || "",
    };
    const services = form.id ? data.services.map((s) => (s.id === form.id ? rec : s)) : [rec, ...data.services];
    // odometer unit ikut naik bila catatan lebih tinggi
    const units = data.units.map((u) =>
      u.id === rec.unitId && rec.odometer > u.odometer ? { ...u, odometer: rec.odometer } : u
    );
    persist({ ...data, services, units });
    closeSheet();
  };

  const savePart = () => {
    if (!form.unitId || !form.nama) return;
    const rec = {
      id: form.id || uid(),
      unitId: form.unitId,
      nama: form.nama,
      kmPasang: num(form.kmPasang),
      lifetimeKm: num(form.lifetimeKm),
      lifetimeBulan: num(form.lifetimeBulan),
      tglPasang: form.tglPasang || todayISO(),
      harga: num(form.harga),
    };
    const parts = form.id ? data.parts.map((p) => (p.id === rec.id ? rec : p)) : [...data.parts, rec];
    persist({ ...data, parts });
    closeSheet();
  };

  const gantiPart = (part) => {
    const u = unitById[part.unitId];
    openSheet("part", {
      form: {
        ...part,
        id: undefined,
        kmPasang: u ? u.odometer : part.kmPasang,
        tglPasang: todayISO(),
        _ganti: part.id,
      },
    });
  };

  const saveIncome = () => {
    const rec = {
      id: form.id || uid(),
      unitId: form.unitId || "",
      tanggal: form.tanggal || todayISO(),
      sumber: form.sumber || "Sewa harian",
      jumlah: num(form.jumlah),
      keterangan: form.keterangan || "",
    };
    const incomes = form.id ? data.incomes.map((i) => (i.id === rec.id ? rec : i)) : [rec, ...data.incomes];
    persist({ ...data, incomes });
    closeSheet();
  };

  const saveLoan = () => {
    const rec = {
      id: form.id || uid(),
      kode: (form.kode || "").trim(),
      unitId: form.unitId || "",
      lembaga: form.lembaga || "",
      pokok: num(form.pokok),
      angsuran: num(form.angsuran),
      tenor: num(form.tenor),
      mulai: form.mulai || todayISO().slice(0, 7),
      tglJatuhTempo: num(form.tglJatuhTempo) || 1,
      pembayaran: form.pembayaran || [],
    };
    const loans = form.id ? data.loans.map((l) => (l.id === rec.id ? rec : l)) : [...data.loans, rec];
    persist({ ...data, loans });
    closeSheet();
  };

  const bayarAngsuran = (loan) => {
    const st = loanStatus(loan);
    if (st.lunas) return;
    const loans = data.loans.map((l) =>
      l.id === loan.id
        ? { ...l, pembayaran: [...(l.pembayaran || []), { ke: st.paid + 1, tanggal: todayISO(), jumlah: l.angsuran }] }
        : l
    );
    persist({ ...data, loans });
  };

  const batalBayar = (loan) => {
    const loans = data.loans.map((l) =>
      l.id === loan.id ? { ...l, pembayaran: (l.pembayaran || []).slice(0, -1) } : l
    );
    persist({ ...data, loans });
  };

  const saveKaryawan = () => {
    if (!form.nama) return;
    const rec = {
      id: form.id || uid(),
      nrp: (form.nrp || "").trim(),
      nama: form.nama.trim(),
      jabatan: form.jabatan || "Sopir",
      statusKerja: form.statusKerja || "Tetap",
      unitId: form.unitId || "",
      hp: form.hp || "",
      jenisSIM: form.jenisSIM || "",
      simExp: form.simExp || "",
      tglMasuk: form.tglMasuk || todayISO(),
      gajiPokok: num(form.gajiPokok),
      tunjangan: num(form.tunjangan),
      aktif: form.aktif === false ? false : true,
    };
    const list = data.karyawan || [];
    const karyawan = form.id ? list.map((k) => (k.id === rec.id ? rec : k)) : [...list, rec];
    persist({ ...data, karyawan });
    closeSheet();
  };

  const imporKaryawan = () => {
    const { hasil } = parseKaryawan(form.teks, data.units);
    const lama = data.karyawan || [];
    const adaNrp = new Set(lama.map((k) => (k.nrp || "").toUpperCase()).filter(Boolean));
    const baru = hasil.filter((k) => !k.nrp || !adaNrp.has(k.nrp.toUpperCase()));
    if (!baru.length) return;
    persist({ ...data, karyawan: [...lama, ...baru] });
    closeSheet();
  };

  const hapus = (koleksi, id) => {
    persist({ ...data, [koleksi]: data[koleksi].filter((x) => x.id !== id) });
  };

  const saveOdo = () => {
    const units = data.units.map((u) => (u.id === form.id ? { ...u, odometer: num(form.odometer) } : u));
    persist({ ...data, units });
    closeSheet();
  };

  /* ---------- ringkasan ---------- */

  const bulanIni = monthKey(todayISO());

  const ringkas = useMemo(() => {
    const ids = new Set(visibleUnits.map((u) => u.id));
    const svc = data.services.filter((s) => ids.has(s.unitId));
    const inc = data.incomes.filter((i) => !i.unitId || ids.has(i.unitId));
    const biayaBulan = svc.filter((s) => monthKey(s.tanggal) === bulanIni).reduce((a, s) => a + s.biaya, 0);
    const masukBulan = inc.filter((i) => monthKey(i.tanggal) === bulanIni).reduce((a, i) => a + i.jumlah, 0);
    const angsuranBulan = data.loans
      .filter((l) => !l.unitId || ids.has(l.unitId))
      .filter((l) => !loanStatus(l).lunas)
      .reduce((a, l) => a + l.angsuran, 0);
    const partAlert = data.parts
      .filter((p) => ids.has(p.unitId))
      .map((p) => ({ p, st: partStatus(p, unitById[p.unitId]) }))
      .filter((x) => x.st.level !== "aman")
      .sort((a, b) => b.st.pct - a.st.pct);
    const jatuhTempo = data.loans
      .map((l) => ({ l, st: loanStatus(l) }))
      .filter((x) => !x.st.lunas && (x.st.level === "telat" || x.st.level === "segera"))
      .sort((a, b) => a.st.selisih - b.st.selisih);
    const kru = (data.karyawan || []).filter((k) => k.aktif !== false);
    const gajiBulan = kru.reduce((a, k) => a + totalGaji(k), 0);
    const simAlert = kru
      .map((k) => ({ k, st: simStatus(k) }))
      .filter((x) => x.st && x.st.level !== "aman")
      .sort((a, b) => a.st.sisaHari - b.st.sisaHari);
    const asuransiAlert = (data.asuransi || [])
      .map((a) => ({ a, st: asuransiStatus(a), unit: data.units.find((u) => u.asuransiId === a.id) }))
      .filter((x) => x.st && x.st.level !== "aman")
      .sort((a, b) => a.st.sisaHari - b.st.sisaHari);
    return {
      biayaBulan,
      masukBulan,
      angsuranBulan,
      gajiBulan,
      jumlahKru: kru.length,
      simAlert,
      asuransiAlert,
      laba: masukBulan - biayaBulan - angsuranBulan - gajiBulan,
      partAlert,
      jatuhTempo,
    };
  }, [data, visibleUnits, unitById, bulanIni]);

  const chartData = useMemo(() => {
    const keys = [];
    for (let i = 5; i >= 0; i--) keys.push(addMonths(bulanIni, -i));
    const ids = new Set(visibleUnits.map((u) => u.id));
    return keys.map((k) => ({
      bulan: monthLabel(k),
      Pemasukan: data.incomes
        .filter((i) => monthKey(i.tanggal) === k && (!i.unitId || ids.has(i.unitId)))
        .reduce((a, i) => a + i.jumlah, 0),
      Biaya: data.services
        .filter((s) => monthKey(s.tanggal) === k && ids.has(s.unitId))
        .reduce((a, s) => a + s.biaya, 0),
    }));
  }, [data, visibleUnits, bulanIni]);

  const biayaPerUnit = useMemo(() => {
    return visibleUnits
      .map((u) => ({
        unit: u.kode,
        Biaya: data.services.filter((s) => s.unitId === u.id).reduce((a, s) => a + s.biaya, 0),
      }))
      .sort((a, b) => b.Biaya - a.Biaya);
  }, [visibleUnits, data.services]);

  /* ---------- render ---------- */

  if (loading) {
    return (
      <div className="app">
        <Style />
        <div className="loading">Memuat data armada…</div>
      </div>
    );
  }

  const kosong = data.units.length === 0;

  return (
    <div
      className="app"
      data-tambah={boleh(user, "tambah") ? "1" : "0"}
      data-hapus={boleh(user, "hapus") ? "1" : "0"}
    >
      <Style />

      <header className="topbar">
        <div className="top-row">
          <div>
            <p className="eyebrow">Buku servis armada</p>
            <h1>Monitoring LV &amp; Bus</h1>
          </div>
          <div className="top-user">
            <span className="who">{user ? user.nama || user.username : ""}</span>
            <span className={`sync ${saving ? "sync-on" : ""}`}>{saving ? "Menyimpan…" : "Tersimpan"}</span>
            <button className="btn btn-ghost sm keluar" onClick={onLogout}>
              Keluar
            </button>
          </div>
        </div>
        <div className="seg">
          {["Semua", "LV", "Bus"].map((t) => (
            <button
              key={t}
              className={filterTipe === t ? "seg-on" : ""}
              onClick={() => setFilterTipe(t)}
            >
              {t}
            </button>
          ))}
        </div>
      </header>

      <main className="main">
        {err ? <div className="banner-err">{err}</div> : null}
        {kosong ? (
          <div className="panel">
            <Empty
              title="Belum ada unit terdaftar. Tambah unit dulu, atau muat contoh data untuk melihat cara kerjanya."
              action="Muat contoh data"
              onAction={() => persist(seed())}
            />
            <button className="btn btn-ghost full" onClick={() => openSheet("unit", { form: { tipe: "LV" } })}>
              Tambah unit sendiri
            </button>
          </div>
        ) : (
          <>
            {tab === "dash" && (
              <Dashboard
                ringkas={ringkas}
                chartData={chartData}
                biayaPerUnit={biayaPerUnit}
                unitById={unitById}
                onGoPart={() => setTab("part")}
                onGoUang={() => setTab("uang")}
                onGoKru={() => { setSubMaster("karyawan"); setTab("master"); }}
                onGoAsuransi={() => { setSubMaster("asuransi"); setTab("master"); }}
                jumlahUnit={visibleUnits.length}
              />
            )}

            {tab === "unit" && (
              <UnitList
                units={visibleUnits}
                data={data}
                unitById={unitById}
                onAdd={() => openSheet("unit", { form: { tipe: filterTipe === "Bus" ? "Bus" : "LV" } })}
                onEdit={(u) =>
                  openSheet("unit", {
                    form: { ...u, loanId: (data.loans.find((l) => l.unitId === u.id) || {}).id || "" },
                  })
                }
                onOdo={(u) => openSheet("odo", { form: { id: u.id, odometer: u.odometer, kode: u.kode } })}
                onDelete={(u) => hapus("units", u.id)}
              />
            )}

            {tab === "servis" && (
              <ServiceList
                data={data}
                unitById={unitById}
                visibleUnits={visibleUnits}
                onAdd={() =>
                  openSheet("servis", {
                    form: { tanggal: todayISO(), jenis: "Servis rutin", unitId: visibleUnits[0] && visibleUnits[0].id },
                  })
                }
                onDelete={(s) => hapus("services", s.id)}
              />
            )}

            {tab === "part" && (
              <PartList
                data={data}
                unitById={unitById}
                visibleUnits={visibleUnits}
                onAdd={() =>
                  openSheet("part", {
                    form: {
                      tglPasang: todayISO(),
                      unitId: visibleUnits[0] && visibleUnits[0].id,
                      kmPasang: visibleUnits[0] ? visibleUnits[0].odometer : 0,
                    },
                  })
                }
                onGanti={gantiPart}
                onEdit={(p) => openSheet("part", { form: { ...p } })}
                onDelete={(p) => hapus("parts", p.id)}
              />
            )}

            {tab === "master" && (
              <MasterData
                user={user}
                data={data}
                unitById={unitById}
                sub={subMaster}
                setSub={setSubMaster}
                onAddKaryawan={() =>
                  openSheet("karyawan", { form: { jabatan: "Sopir", statusKerja: "Tetap", tglMasuk: todayISO() } })
                }
                onEditKaryawan={(k) => openSheet("karyawan", { form: { ...k } })}
                onDelKaryawan={(k) => hapus("karyawan", k.id)}
                onImportKaryawan={() => openSheet("impor", { form: { teks: "" } })}
                onSeedKaryawan={() => persist({ ...data, karyawan: seed().karyawan })}
                onAddUnit={() => openSheet("unit", { form: { tipe: "LV" } })}
                onEditUnit={(u) =>
                  openSheet("unit", {
                    form: { ...u, loanId: (data.loans.find((l) => l.unitId === u.id) || {}).id || "" },
                  })
                }
                onDelUnit={(u) => hapus("units", u.id)}
                onAddMerek={() => openSheet("merek", { form: { tipe: "Umum" } })}
                onEditMerek={(m) => openSheet("merek", { form: { ...m } })}
                onDelMerek={(m) => hapus("merek", m.id)}
                onAddAsuransi={() => openSheet("asuransi", { form: { jenis: "All Risk", mulai: todayISO() } })}
                onEditAsuransi={(a) => openSheet("asuransi", { form: { ...a } })}
                onDelAsuransi={(a) => hapus("asuransi", a.id)}
                onAddLoan={() =>
                  openSheet("loan", { form: { mulai: todayISO().slice(0, 7), tglJatuhTempo: 10, tenor: 48 } })
                }
                onEditLoan={(l) => openSheet("loan", { form: { ...l } })}
                onDelLoan={(l) => hapus("loans", l.id)}
              />
            )}

            {tab === "uang" && (
              <Money
                data={data}
                unitById={unitById}
                visibleUnits={visibleUnits}
                onAddIncome={() =>
                  openSheet("income", { form: { tanggal: todayISO(), sumber: "Sewa harian" } })
                }
                onAddLoan={() =>
                  openSheet("loan", { form: { mulai: todayISO().slice(0, 7), tglJatuhTempo: 10, tenor: 48 } })
                }
                onBayar={bayarAngsuran}
                onBatal={batalBayar}
                onDelIncome={(i) => hapus("incomes", i.id)}
                onDelLoan={(l) => hapus("loans", l.id)}
              />
            )}
          </>
        )}
      </main>

      <nav className="tabbar">
        {[
          ["dash", "Ringkas", "ringkas"],
          ["unit", "Unit", "unit"],
          ["servis", "Servis", "servis"],
          ["part", "Part", "part"],
          ["uang", "Keuangan", "keuangan"],
          ["master", "Master", "master"],
        ]
          .filter(([, , hak]) => boleh(user, hak))
          .map(([k, label]) => (
          <button key={k} className={tab === k ? "tab-on" : ""} onClick={() => setTab(k)}>
            <TabIcon name={k} active={tab === k} />
            <span>{label}</span>
          </button>
        ))}
      </nav>

      {/* ---------------- SHEETS ---------------- */}

      <Sheet
        open={sheet && sheet.kind === "unit"}
        title={form.id ? "Ubah unit" : "Unit baru"}
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveUnit}>
              Simpan unit
            </button>
          </>
        }
      >
        <div className="row2">
          <Field label="Kode unit" hint="LV-03">
            <input value={form.kode || ""} onChange={set("kode")} placeholder="LV-03" />
          </Field>
          <Field label="Nomor lambung">
            <input value={form.noLambung || ""} onChange={set("noLambung")} placeholder="L-103" />
          </Field>
        </div>
        <div className="row2">
          <Field label="Jenis">
            <select value={form.tipe || "LV"} onChange={set("tipe")}>
              <option>LV</option>
              <option>Bus</option>
            </select>
          </Field>
          <Field label="Merek">
            <select value={form.merek || ""} onChange={set("merek")}>
              <option value="">— Pilih merek —</option>
              {(data.merek || []).map((m) => (
                <option key={m.id} value={m.nama}>
                  {m.nama}
                </option>
              ))}
              {form.merek && !(data.merek || []).some((m) => m.nama === form.merek) ? (
                <option value={form.merek}>{form.merek}</option>
              ) : null}
            </select>
          </Field>
        </div>
        {(data.merek || []).length === 0 ? (
          <p className="muted small nudge">Daftar merek masih kosong. Isi lewat Master › Merek agar bisa dipilih di sini.</p>
        ) : null}
        <Field label="Tipe / model">
          <input value={form.model || ""} onChange={set("model")} placeholder="Hilux D-Cab" />
        </Field>
        <div className="row2">
          <Field label="Nomor polisi">
            <input value={form.nopol || ""} onChange={set("nopol")} placeholder="B 1234 ABC" />
          </Field>
          <Field label="Tahun">
            <input inputMode="numeric" value={form.tahun || ""} onChange={set("tahun")} placeholder="2022" />
          </Field>
        </div>
        <Field label="Odometer (km)">
          <input inputMode="numeric" value={form.odometer || ""} onChange={set("odometer")} placeholder="86400" />
        </Field>
        <Field label="Kode pinjaman" hint="kontrak pembiayaan">
          <select value={form.loanId || ""} onChange={set("loanId")}>
            <option value="">— Tanpa pembiayaan —</option>
            {data.loans.map((l) => (
              <option key={l.id} value={l.id}>
                {l.kode || "tanpa kode"} — {l.lembaga}
              </option>
            ))}
          </select>
        </Field>
        <Field label="Polis asuransi">
          <select value={form.asuransiId || ""} onChange={set("asuransiId")}>
            <option value="">— Belum diasuransikan —</option>
            {(data.asuransi || []).map((a) => (
              <option key={a.id} value={a.id}>
                {a.kode || a.nomorPolis} — {a.penanggung}
              </option>
            ))}
          </select>
        </Field>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "merek"}
        title={form.id ? "Ubah merek" : "Merek baru"}
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveMerek}>
              Simpan merek
            </button>
          </>
        }
      >
        <Field label="Nama merek">
          <input value={form.nama || ""} onChange={set("nama")} placeholder="Hino" />
        </Field>
        <Field label="Dipakai untuk">
          <select value={form.tipe || "Umum"} onChange={set("tipe")}>
            <option>Umum</option>
            <option>LV</option>
            <option>Bus</option>
          </select>
        </Field>
        <Field label="Agen / dealer">
          <input value={form.agen || ""} onChange={set("agen")} placeholder="Hino Sales Indonesia" />
        </Field>
        <Field label="Tipe yang dipakai">
          <input value={form.keterangan || ""} onChange={set("keterangan")} placeholder="RK8, RN285" />
        </Field>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "asuransi"}
        title={form.id ? "Ubah polis" : "Polis asuransi baru"}
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveAsuransi}>
              Simpan polis
            </button>
          </>
        }
      >
        <div className="row2">
          <Field label="Kode asuransi">
            <input value={form.kode || ""} onChange={set("kode")} placeholder="ASR-005" />
          </Field>
          <Field label="Jenis pertanggungan">
            <select value={form.jenis || "All Risk"} onChange={set("jenis")}>
              <option>All Risk</option>
              <option>TLO</option>
              <option>All Risk + Rider</option>
              <option>Kombinasi</option>
            </select>
          </Field>
        </div>
        <Field label="Perusahaan penanggung">
          <input value={form.penanggung || ""} onChange={set("penanggung")} placeholder="Garda Oto" />
        </Field>
        <Field label="Nomor polis">
          <input value={form.nomorPolis || ""} onChange={set("nomorPolis")} placeholder="GO-2405-88121" />
        </Field>
        <div className="row2">
          <Field label="Mulai berlaku">
            <input type="date" value={form.mulai || ""} onChange={set("mulai")} />
          </Field>
          <Field label="Berakhir">
            <input type="date" value={form.berakhir || ""} onChange={set("berakhir")} />
          </Field>
        </div>
        <Field label="Premi per tahun (Rp)">
          <input inputMode="numeric" value={form.premi || ""} onChange={set("premi")} />
        </Field>
        <Field label="Catatan">
          <input value={form.keterangan || ""} onChange={set("keterangan")} placeholder="Termasuk tanggung jawab pihak ketiga" />
        </Field>
        <p className="muted small nudge">Polis dipasangkan ke unit lewat formulir unit.</p>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "odo"}
        title={`Perbarui odometer ${form.kode || ""}`}
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveOdo}>
              Simpan
            </button>
          </>
        }
      >
        <Field label="Odometer sekarang (km)" hint="dipakai untuk hitung umur part">
          <input inputMode="numeric" value={form.odometer || ""} onChange={set("odometer")} />
        </Field>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "servis"}
        title="Catat perawatan"
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveService}>
              Simpan catatan
            </button>
          </>
        }
      >
        <Field label="Unit">
          <select value={form.unitId || ""} onChange={set("unitId")}>
            {data.units.map((u) => (
              <option key={u.id} value={u.id}>
                {u.kode} — {u.model}
              </option>
            ))}
          </select>
        </Field>
        <div className="row2">
          <Field label="Tanggal">
            <input type="date" value={form.tanggal || todayISO()} onChange={set("tanggal")} />
          </Field>
          <Field label="Jenis">
            <select value={form.jenis || "Servis rutin"} onChange={set("jenis")}>
              <option>Servis rutin</option>
              <option>Perbaikan</option>
              <option>Ban</option>
              <option>Body &amp; cat</option>
              <option>KIR / perizinan</option>
              <option>Lainnya</option>
            </select>
          </Field>
        </div>
        <div className="row2">
          <Field label="Odometer (km)">
            <input inputMode="numeric" value={form.odometer || ""} onChange={set("odometer")} />
          </Field>
          <Field label="Biaya (Rp)">
            <input inputMode="numeric" value={form.biaya || ""} onChange={set("biaya")} />
          </Field>
        </div>
        <Field label="Pekerjaan">
          <input value={form.deskripsi || ""} onChange={set("deskripsi")} placeholder="Ganti oli mesin + filter" />
        </Field>
        <Field label="Bengkel / vendor">
          <input value={form.vendor || ""} onChange={set("vendor")} placeholder="Workshop internal" />
        </Field>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "part"}
        title={form._ganti ? "Ganti part" : form.id ? "Ubah part" : "Part baru"}
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={savePart}>
              Simpan part
            </button>
          </>
        }
      >
        <Field label="Unit">
          <select value={form.unitId || ""} onChange={set("unitId")}>
            {data.units.map((u) => (
              <option key={u.id} value={u.id}>
                {u.kode} — {u.model}
              </option>
            ))}
          </select>
        </Field>
        <Field label="Nama part">
          <input value={form.nama || ""} onChange={set("nama")} placeholder="Kampas rem depan" />
        </Field>
        <div className="row2">
          <Field label="Dipasang di km">
            <input inputMode="numeric" value={form.kmPasang || ""} onChange={set("kmPasang")} />
          </Field>
          <Field label="Tanggal pasang">
            <input type="date" value={form.tglPasang || todayISO()} onChange={set("tglPasang")} />
          </Field>
        </div>
        <div className="row2">
          <Field label="Umur pakai (km)" hint="0 = tak dipakai">
            <input inputMode="numeric" value={form.lifetimeKm || ""} onChange={set("lifetimeKm")} placeholder="30000" />
          </Field>
          <Field label="Umur pakai (bulan)" hint="0 = tak dipakai">
            <input inputMode="numeric" value={form.lifetimeBulan || ""} onChange={set("lifetimeBulan")} placeholder="24" />
          </Field>
        </div>
        <Field label="Harga part (Rp)">
          <input inputMode="numeric" value={form.harga || ""} onChange={set("harga")} />
        </Field>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "karyawan"}
        title={form.id ? "Ubah data kru" : "Kru baru"}
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveKaryawan}>
              Simpan kru
            </button>
          </>
        }
      >
        <div className="row2">
          <Field label="NRP" hint="nomor induk">
            <input value={form.nrp || ""} onChange={set("nrp")} placeholder="NRP-0042" />
          </Field>
          <Field label="Nama lengkap">
            <input value={form.nama || ""} onChange={set("nama")} placeholder="Nama karyawan" />
          </Field>
        </div>
        <div className="row2">
          <Field label="Jabatan">
            <select value={form.jabatan || "Sopir"} onChange={set("jabatan")}>
              <option>Sopir</option>
              <option>Kernet</option>
              <option>Mekanik</option>
              <option>Kepala bengkel</option>
              <option>Admin</option>
              <option>Lainnya</option>
            </select>
          </Field>
          <Field label="Status kerja">
            <select value={form.statusKerja || "Tetap"} onChange={set("statusKerja")}>
              <option>Tetap</option>
              <option>Kontrak</option>
              <option>Harian</option>
              <option>Non-aktif</option>
            </select>
          </Field>
        </div>
        <Field label="Unit yang dipegang" hint="boleh kosong">
          <select value={form.unitId || ""} onChange={set("unitId")}>
            <option value="">— Tanpa unit —</option>
            {data.units.map((u) => (
              <option key={u.id} value={u.id}>
                {u.kode} — {u.model}
              </option>
            ))}
          </select>
        </Field>
        <div className="row2">
          <Field label="Nomor HP">
            <input value={form.hp || ""} onChange={set("hp")} placeholder="0812-xxxx-xxxx" />
          </Field>
          <Field label="Tanggal masuk">
            <input type="date" value={form.tglMasuk || todayISO()} onChange={set("tglMasuk")} />
          </Field>
        </div>
        <div className="row2">
          <Field label="Jenis SIM">
            <select value={form.jenisSIM || ""} onChange={set("jenisSIM")}>
              <option value="">— Tidak ada —</option>
              <option>SIM A</option>
              <option>SIM A Umum</option>
              <option>SIM B1</option>
              <option>SIM B1 Umum</option>
              <option>SIM B2</option>
              <option>SIM B2 Umum</option>
            </select>
          </Field>
          <Field label="Berlaku sampai">
            <input type="date" value={form.simExp || ""} onChange={set("simExp")} />
          </Field>
        </div>
        <div className="row2">
          <Field label="Gaji pokok (Rp)">
            <input inputMode="numeric" value={form.gajiPokok || ""} onChange={set("gajiPokok")} />
          </Field>
          <Field label="Tunjangan (Rp)">
            <input inputMode="numeric" value={form.tunjangan || ""} onChange={set("tunjangan")} />
          </Field>
        </div>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "impor"}
        title="Tempel data karyawan"
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={imporKaryawan}>
              Masukkan {parseKaryawan(form.teks, data.units).hasil.length} orang
            </button>
          </>
        }
      >
        <p className="hint-box">
          Satu baris per orang, kolom dipisah tanda <b>|</b> (boleh juga tab atau koma). Urutan kolom:
          <br />
          <span className="mono small">
            NRP | Nama | Jabatan | Kode unit | Status | HP | Jenis SIM | Berlaku sampai | Gaji pokok | Tunjangan
          </span>
          <br />
          Kolom yang tidak ada boleh dikosongkan. Tanggal: 2027-03-14 atau 14/03/2027.
        </p>
        <Field label="Data">
          <textarea
            rows={8}
            value={form.teks || ""}
            onChange={set("teks")}
            placeholder={"NRP-0004 | Warsito | Sopir | BUS-01 | Tetap | 0857-2211-9080 | SIM B2 Umum | 14/03/2027 | 6200000 | 1800000"}
          />
        </Field>
        {form.teks ? (
          <p className="muted small">
            Terbaca {parseKaryawan(form.teks, data.units).hasil.length} baris. Nama unit yang tidak cocok akan
            disimpan tanpa unit — bisa dipasangkan lewat tombol Ubah.
          </p>
        ) : null}
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "income"}
        title="Catat pemasukan"
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveIncome}>
              Simpan pemasukan
            </button>
          </>
        }
      >
        <Field label="Unit" hint="boleh kosong">
          <select value={form.unitId || ""} onChange={set("unitId")}>
            <option value="">— Tanpa unit —</option>
            {data.units.map((u) => (
              <option key={u.id} value={u.id}>
                {u.kode} — {u.model}
              </option>
            ))}
          </select>
        </Field>
        <div className="row2">
          <Field label="Tanggal">
            <input type="date" value={form.tanggal || todayISO()} onChange={set("tanggal")} />
          </Field>
          <Field label="Sumber">
            <select value={form.sumber || "Sewa harian"} onChange={set("sumber")}>
              <option>Sewa harian</option>
              <option>Sewa bulanan</option>
              <option>Kontrak</option>
              <option>Trip / rit</option>
              <option>Lainnya</option>
            </select>
          </Field>
        </div>
        <Field label="Jumlah (Rp)">
          <input inputMode="numeric" value={form.jumlah || ""} onChange={set("jumlah")} />
        </Field>
        <Field label="Keterangan">
          <input value={form.keterangan || ""} onChange={set("keterangan")} />
        </Field>
      </Sheet>

      <Sheet
        open={sheet && sheet.kind === "loan"}
        title="Angsuran baru"
        onClose={closeSheet}
        footer={
          <>
            <button className="btn btn-ghost" onClick={closeSheet}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={saveLoan}>
              Simpan angsuran
            </button>
          </>
        }
      >
        <Field label="Unit">
          <select value={form.unitId || ""} onChange={set("unitId")}>
            <option value="">— Tanpa unit —</option>
            {data.units.map((u) => (
              <option key={u.id} value={u.id}>
                {u.kode} — {u.model}
              </option>
            ))}
          </select>
        </Field>
        <div className="row2">
          <Field label="Kode pinjaman">
            <input value={form.kode || ""} onChange={set("kode")} placeholder="KRD-2026-003" />
          </Field>
          <Field label="Lembaga pembiayaan">
            <input value={form.lembaga || ""} onChange={set("lembaga")} placeholder="BCA Finance" />
          </Field>
        </div>
        <div className="row2">
          <Field label="Pokok pinjaman (Rp)">
            <input inputMode="numeric" value={form.pokok || ""} onChange={set("pokok")} />
          </Field>
          <Field label="Angsuran / bulan (Rp)">
            <input inputMode="numeric" value={form.angsuran || ""} onChange={set("angsuran")} />
          </Field>
        </div>
        <div className="row2">
          <Field label="Tenor (bulan)">
            <input inputMode="numeric" value={form.tenor || ""} onChange={set("tenor")} />
          </Field>
          <Field label="Jatuh tempo tiap tgl">
            <input inputMode="numeric" value={form.tglJatuhTempo || ""} onChange={set("tglJatuhTempo")} />
          </Field>
        </div>
        <Field label="Mulai angsuran" hint="bulan pertama">
          <input type="month" value={form.mulai || ""} onChange={set("mulai")} />
        </Field>
      </Sheet>
    </div>
  );
}

/* ============================== VIEWS ============================== */

function Dashboard({ ringkas, chartData, biayaPerUnit, jumlahUnit, onGoPart, onGoUang, onGoKru, onGoAsuransi }) {
  const labaPositif = ringkas.laba >= 0;
  return (
    <>
      <section className="panel">
        <div className="stat-grid">
          <div className="stat">
            <span className="stat-k">Unit dipantau</span>
            <span className="stat-v mono">{jumlahUnit}</span>
          </div>
          <div className="stat">
            <span className="stat-k">Pemasukan bulan ini</span>
            <span className="stat-v mono pos">{rp(ringkas.masukBulan)}</span>
          </div>
          <div className="stat">
            <span className="stat-k">Biaya perawatan</span>
            <span className="stat-v mono neg">{rp(ringkas.biayaBulan)}</span>
          </div>
          <div className="stat">
            <span className="stat-k">Angsuran bulan ini</span>
            <span className="stat-v mono neg">{rp(ringkas.angsuranBulan)}</span>
          </div>
          <div className="stat stat-wide" onClick={onGoKru}>
            <span className="stat-k">Gaji kru ({ringkas.jumlahKru} orang)</span>
            <span className="stat-v mono neg">{rp(ringkas.gajiBulan)}</span>
          </div>
        </div>
        <div className={`netto ${labaPositif ? "netto-pos" : "netto-neg"}`}>
          <span>Sisa kas bulan ini</span>
          <strong className="mono">{rp(ringkas.laba)}</strong>
        </div>
      </section>

      <section className="panel">
        <h2>Perlu tindakan</h2>
        {ringkas.partAlert.length === 0 &&
        ringkas.jatuhTempo.length === 0 &&
        ringkas.simAlert.length === 0 &&
        ringkas.asuransiAlert.length === 0 ? (
          <p className="muted">
            Semua part masih dalam umur pakai, SIM kru dan polis asuransi berlaku, dan tidak ada angsuran mendekati
            jatuh tempo.
          </p>
        ) : (
          <ul className="alerts">
            {ringkas.partAlert.slice(0, 4).map(({ p, st }) => (
              <li key={p.id} onClick={onGoPart}>
                <Tag tone={st.level === "lewat" ? "bahaya" : "waspada"}>
                  {st.level === "lewat" ? "Lewat umur" : "Segera ganti"}
                </Tag>
                <span className="alert-txt">{p.nama}</span>
                <span className="mono small">
                  {st.sisaKm !== null && st.basis === "jarak"
                    ? st.sisaKm >= 0
                      ? `sisa ${km(st.sisaKm)}`
                      : `lewat ${km(-st.sisaKm)}`
                    : st.sisaHari >= 0
                    ? `sisa ${st.sisaHari} hari`
                    : `lewat ${-st.sisaHari} hari`}
                </span>
              </li>
            ))}
            {ringkas.simAlert.slice(0, 3).map(({ k, st }) => (
              <li key={k.id} onClick={onGoKru}>
                <Tag tone={st.level === "lewat" ? "bahaya" : "waspada"}>
                  {st.level === "lewat" ? "SIM habis" : "SIM segera habis"}
                </Tag>
                <span className="alert-txt">
                  {k.nama} · {k.jenisSIM}
                </span>
                <span className="mono small">
                  {st.sisaHari >= 0 ? `${st.sisaHari} hari` : `lewat ${-st.sisaHari} hari`}
                </span>
              </li>
            ))}
            {ringkas.asuransiAlert.slice(0, 3).map(({ a, st, unit }) => (
              <li key={a.id} onClick={onGoAsuransi}>
                <Tag tone={st.level === "lewat" ? "bahaya" : "waspada"}>
                  {st.level === "lewat" ? "Polis habis" : "Polis segera habis"}
                </Tag>
                <span className="alert-txt">
                  {unit ? `${unit.kode} · ` : ""}
                  {a.penanggung}
                </span>
                <span className="mono small">
                  {st.sisaHari >= 0 ? `${st.sisaHari} hari` : `lewat ${-st.sisaHari} hari`}
                </span>
              </li>
            ))}
            {ringkas.jatuhTempo.slice(0, 3).map(({ l, st }) => (
              <li key={l.id} onClick={onGoUang}>
                <Tag tone={st.level === "telat" ? "bahaya" : "waspada"}>
                  {st.level === "telat" ? "Telat bayar" : "Jatuh tempo"}
                </Tag>
                <span className="alert-txt">{l.lembaga || "Angsuran"}</span>
                <span className="mono small">{fmtDate(st.dueISO)}</span>
              </li>
            ))}
          </ul>
        )}
      </section>

      <section className="panel">
        <h2>Pemasukan vs biaya perawatan</h2>
        <div className="chart">
          <MiniChart rows={chartData} />
        </div>
      </section>

      <section className="panel">
        <h2>Biaya perawatan kumulatif per unit</h2>
        {biayaPerUnit.length === 0 ? (
          <p className="muted">Belum ada catatan perawatan.</p>
        ) : (
          <ul className="rank">
            {biayaPerUnit.map((r) => {
              const max = biayaPerUnit[0].Biaya || 1;
              return (
                <li key={r.unit}>
                  <span className="rank-k">{r.unit}</span>
                  <span className="rank-bar">
                    <i style={{ width: `${(r.Biaya / max) * 100}%` }} />
                  </span>
                  <span className="mono small">{rpShort(r.Biaya)}</span>
                </li>
              );
            })}
          </ul>
        )}
      </section>
    </>
  );
}

function UnitList({ units, data, onAdd, onEdit, onOdo, onDelete, unitById }) {
  return (
    <>
      <div className="toolbar">
        <h2>Daftar unit</h2>
        <button className="btn btn-primary sm" onClick={onAdd}>
          + Unit
        </button>
      </div>
      {units.length === 0 ? (
        <div className="panel">
          <Empty title="Tidak ada unit pada filter ini." />
        </div>
      ) : (
        units.map((u) => {
          const svc = data.services.filter((s) => s.unitId === u.id);
          const biaya = svc.reduce((a, s) => a + s.biaya, 0);
          const masuk = data.incomes.filter((i) => i.unitId === u.id).reduce((a, i) => a + i.jumlah, 0);
          const alerts = data.parts
            .filter((p) => p.unitId === u.id)
            .map((p) => partStatus(p, unitById[p.unitId]))
            .filter((s) => s.level !== "aman").length;
          const terakhir = svc.slice().sort((a, b) => (a.tanggal < b.tanggal ? 1 : -1))[0];
          return (
            <article className="card" key={u.id}>
              <div className="card-head">
                <div>
                  <span className="unit-kode">{u.kode}</span>
                  <span className={`chip ${u.tipe === "Bus" ? "chip-bus" : "chip-lv"}`}>{u.tipe}</span>
                </div>
                <Plate>{u.nopol || "—"}</Plate>
              </div>
              <p className="card-sub">
                {[u.merek, u.model].filter(Boolean).join(" ") || "—"} · {u.tahun || "—"} · lambung{" "}
                {u.noLambung || "—"}
                <br />
                {(() => {
                  const kru = (data.karyawan || []).filter((k) => k.unitId === u.id && k.aktif !== false);
                  if (kru.length) return kru.map((k) => `${k.nama} (${k.jabatan})`).join(", ");
                  return "belum ada kru";
                })()}
              </p>
              <div className="odo-row" onClick={() => onOdo(u)}>
                <span className="odo-label">Odometer</span>
                <span className="odo-val mono">{(u.odometer || 0).toLocaleString("id-ID")}</span>
                <span className="odo-unit">km</span>
                <span className="odo-edit">ubah</span>
              </div>
              <div className="mini-grid">
                <div>
                  <span className="mini-k">Biaya perawatan</span>
                  <span className="mini-v mono neg">{rpShort(biaya)}</span>
                </div>
                <div>
                  <span className="mini-k">Pemasukan</span>
                  <span className="mini-v mono pos">{rpShort(masuk)}</span>
                </div>
                <div>
                  <span className="mini-k">Part perlu cek</span>
                  <span className={`mini-v mono ${alerts ? "neg" : ""}`}>{alerts}</span>
                </div>
              </div>
              <p className="muted small">
                Servis terakhir: {terakhir ? `${fmtDate(terakhir.tanggal)} — ${terakhir.deskripsi}` : "belum ada"}
              </p>
              <div className="card-actions">
                <button className="btn btn-ghost sm act-ubah" onClick={() => onEdit(u)}>
                  Ubah
                </button>
                <button className="btn btn-danger sm" onClick={() => onDelete(u)}>
                  Hapus
                </button>
              </div>
            </article>
          );
        })
      )}
    </>
  );
}

function ServiceList({ data, unitById, visibleUnits, onAdd, onDelete }) {
  const ids = new Set(visibleUnits.map((u) => u.id));
  const list = data.services
    .filter((s) => ids.has(s.unitId))
    .slice()
    .sort((a, b) => (a.tanggal < b.tanggal ? 1 : -1));
  const total = list.reduce((a, s) => a + s.biaya, 0);
  return (
    <>
      <div className="toolbar">
        <h2>Riwayat perawatan</h2>
        <button className="btn btn-primary sm" onClick={onAdd}>
          + Catatan
        </button>
      </div>
      <div className="panel tight">
        <div className="totline">
          <span>{list.length} catatan</span>
          <strong className="mono">{rp(total)}</strong>
        </div>
      </div>
      {list.length === 0 ? (
        <div className="panel">
          <Empty title="Belum ada catatan perawatan untuk filter ini." action="Catat perawatan" onAction={onAdd} />
        </div>
      ) : (
        list.map((s) => {
          const u = unitById[s.unitId];
          return (
            <article className="row-card" key={s.id}>
              <div className="row-main">
                <div className="row-top">
                  <span className="unit-kode small">{u ? u.kode : "—"}</span>
                  <span className={`chip chip-${s.jenis === "Perbaikan" ? "warn" : "plain"}`}>{s.jenis}</span>
                  <span className="muted small">{fmtDate(s.tanggal)}</span>
                </div>
                <p className="row-desc">{s.deskripsi || "—"}</p>
                <p className="muted small">
                  {km(s.odometer)} · {s.vendor || "vendor tidak dicatat"}
                </p>
              </div>
              <div className="row-side">
                <span className="mono neg">{rp(s.biaya)}</span>
                <button className="link-del act-hapus" onClick={() => onDelete(s)}>
                  hapus
                </button>
              </div>
            </article>
          );
        })
      )}
    </>
  );
}

function PartList({ data, unitById, visibleUnits, onAdd, onGanti, onEdit, onDelete }) {
  const ids = new Set(visibleUnits.map((u) => u.id));
  const list = data.parts
    .filter((p) => ids.has(p.unitId))
    .map((p) => ({ p, st: partStatus(p, unitById[p.unitId]) }))
    .sort((a, b) => b.st.pct - a.st.pct);
  return (
    <>
      <div className="toolbar">
        <h2>Umur pakai part</h2>
        <button className="btn btn-primary sm" onClick={onAdd}>
          + Part
        </button>
      </div>
      {list.length === 0 ? (
        <div className="panel">
          <Empty title="Belum ada part yang dipantau. Daftarkan part beserta umur pakainya agar penggantian bisa diingatkan." action="Tambah part" onAction={onAdd} />
        </div>
      ) : (
        list.map(({ p, st }) => {
          const u = unitById[p.unitId];
          return (
            <article className="card" key={p.id}>
              <div className="card-head">
                <div>
                  <span className="part-nama">{p.nama}</span>
                </div>
                <Tag tone={st.level === "lewat" ? "bahaya" : st.level === "segera" ? "waspada" : "aman"}>
                  {st.level === "lewat" ? "Lewat umur" : st.level === "segera" ? "Segera ganti" : "Aman"}
                </Tag>
              </div>
              <p className="card-sub">
                {u ? `${u.kode} · ${u.model}` : "unit terhapus"} · dipasang {fmtDate(p.tglPasang)} di {km(p.kmPasang)}
              </p>

              <Wear pct={st.pct} level={st.level} />

              <div className="wear-legend">
                <span className="mono small">{Math.round(st.pct * 100)}% terpakai</span>
                <span className="mono small">
                  {p.lifetimeKm > 0
                    ? st.sisaKm >= 0
                      ? `sisa ${km(st.sisaKm)}`
                      : `lewat ${km(-st.sisaKm)}`
                    : "—"}
                </span>
                <span className="mono small">
                  {p.lifetimeBulan > 0
                    ? st.sisaHari >= 0
                      ? `sisa ${st.sisaHari} hari`
                      : `lewat ${-st.sisaHari} hari`
                    : "—"}
                </span>
              </div>

              <div className="card-actions">
                <span className="mono small muted">{p.harga ? rp(p.harga) : ""}</span>
                <button className="btn btn-primary sm" onClick={() => onGanti(p)}>
                  Catat penggantian
                </button>
                <button className="btn btn-ghost sm act-ubah" onClick={() => onEdit(p)}>
                  Ubah
                </button>
                <button className="btn btn-danger sm" onClick={() => onDelete(p)}>
                  Hapus
                </button>
              </div>
            </article>
          );
        })
      )}
    </>
  );
}

function Money({ data, unitById, visibleUnits, onAddIncome, onAddLoan, onBayar, onBatal, onDelIncome, onDelLoan }) {
  const [sub, setSub] = useState("masuk");
  const ids = new Set(visibleUnits.map((u) => u.id));
  const incomes = data.incomes
    .filter((i) => !i.unitId || ids.has(i.unitId))
    .slice()
    .sort((a, b) => (a.tanggal < b.tanggal ? 1 : -1));
  const totalMasuk = incomes.reduce((a, i) => a + i.jumlah, 0);

  return (
    <>
      <div className="seg seg-wide">
        <button className={sub === "masuk" ? "seg-on" : ""} onClick={() => setSub("masuk")}>
          Pemasukan
        </button>
        <button className={sub === "angsuran" ? "seg-on" : ""} onClick={() => setSub("angsuran")}>
          Angsuran
        </button>
      </div>

      {sub === "masuk" ? (
        <>
          <div className="toolbar">
            <h2>Pemasukan</h2>
            <button className="btn btn-primary sm" onClick={onAddIncome}>
              + Pemasukan
            </button>
          </div>
          <div className="panel tight">
            <div className="totline">
              <span>{incomes.length} transaksi</span>
              <strong className="mono pos">{rp(totalMasuk)}</strong>
            </div>
          </div>
          {incomes.length === 0 ? (
            <div className="panel">
              <Empty title="Belum ada pemasukan tercatat." action="Catat pemasukan" onAction={onAddIncome} />
            </div>
          ) : (
            incomes.map((i) => (
              <article className="row-card" key={i.id}>
                <div className="row-main">
                  <div className="row-top">
                    <span className="unit-kode small">{i.unitId && unitById[i.unitId] ? unitById[i.unitId].kode : "UMUM"}</span>
                    <span className="chip chip-plain">{i.sumber}</span>
                    <span className="muted small">{fmtDate(i.tanggal)}</span>
                  </div>
                  <p className="row-desc">{i.keterangan || "—"}</p>
                </div>
                <div className="row-side">
                  <span className="mono pos">{rp(i.jumlah)}</span>
                  <button className="link-del" onClick={() => onDelIncome(i)}>
                    hapus
                  </button>
                </div>
              </article>
            ))
          )}
        </>
      ) : (
        <>
          <div className="toolbar">
            <h2>Angsuran kendaraan</h2>
            <button className="btn btn-primary sm" onClick={onAddLoan}>
              + Angsuran
            </button>
          </div>
          {data.loans.length === 0 ? (
            <div className="panel">
              <Empty title="Belum ada angsuran terdaftar." action="Tambah angsuran" onAction={onAddLoan} />
            </div>
          ) : (
            data.loans.map((l) => {
              const st = loanStatus(l);
              const u = l.unitId ? unitById[l.unitId] : null;
              return (
                <article className="card" key={l.id}>
                  <div className="card-head">
                    <div>
                      <span className="part-nama">{l.lembaga || "Pembiayaan"}</span>
                    </div>
                    <Tag tone={st.level === "telat" ? "bahaya" : st.level === "segera" ? "waspada" : st.lunas ? "aman" : "netral"}>
                      {st.lunas ? "Lunas" : st.level === "telat" ? `Telat ${-st.selisih} hari` : `Jatuh tempo ${fmtDate(st.dueISO)}`}
                    </Tag>
                  </div>
                  <p className="card-sub">
                    {u ? `${u.kode} · ${u.model}` : "tanpa unit"} · pokok {rpShort(l.pokok)}
                  </p>

                  <div className="progress">
                    <div className="progress-fill" style={{ width: `${(st.paid / (l.tenor || 1)) * 100}%` }} />
                  </div>
                  <div className="wear-legend">
                    <span className="mono small">
                      {st.paid}/{l.tenor} angsuran
                    </span>
                    <span className="mono small">{rp(l.angsuran)}/bln</span>
                    <span className="mono small">sisa {rpShort(st.sisaBayar)}</span>
                  </div>

                  <div className="card-actions">
                    <button className="btn btn-primary sm" onClick={() => onBayar(l)} disabled={st.lunas}>
                      Catat bayar ke-{st.paid + 1}
                    </button>
                    <button className="btn btn-ghost sm" onClick={() => onBatal(l)} disabled={st.paid === 0}>
                      Batalkan terakhir
                    </button>
                    <button className="btn btn-danger sm" onClick={() => onDelLoan(l)}>
                      Hapus
                    </button>
                  </div>
                </article>
              );
            })
          )}
        </>
      )}
    </>
  );
}

function KaryawanList({ data, unitById, onAdd, onEdit, onDelete, onSeed, onImport }) {
  const list = (data.karyawan || []).slice().sort((a, b) => (a.nrp || "zzz").localeCompare(b.nrp || "zzz") || a.nama.localeCompare(b.nama));
  const totalBulanan = list.filter((k) => k.aktif !== false).reduce((a, k) => a + totalGaji(k), 0);
  return (
    <>
      <div className="toolbar">
        <h2>Kru &amp; karyawan</h2>
        <div className="tool-btns">
          <button className="btn btn-ghost sm" onClick={onImport}>
            Tempel data
          </button>
          <button className="btn btn-primary sm" onClick={onAdd}>
            + Kru
          </button>
        </div>
      </div>
      {list.length === 0 ? (
        <div className="panel">
          <Empty
            title="Belum ada data karyawan. Tambahkan sopir, kernet, mekanik, dan admin beserta gaji serta masa berlaku SIM-nya."
            action="Tambah kru"
            onAction={onAdd}
          />
          <button className="btn btn-ghost full" onClick={onImport}>
            Tempel banyak data sekaligus
          </button>
          <button className="btn btn-ghost full" onClick={onSeed}>
            Muat contoh kru
          </button>
        </div>
      ) : (
        <>
          <div className="panel tight">
            <div className="totline">
              <span>{list.length} orang · beban gaji per bulan</span>
              <strong className="mono neg">{rp(totalBulanan)}</strong>
            </div>
          </div>
          {list.map((k) => {
            const st = simStatus(k);
            const u = k.unitId ? unitById[k.unitId] : null;
            return (
              <article className="card" key={k.id}>
                <div className="card-head">
                  <div>
                    <span className="part-nama">{k.nama}</span>
                    <span className="chip chip-lv">{k.jabatan}</span>
                  </div>
                  {u ? <Plate>{u.kode}</Plate> : <Tag tone="netral">Tanpa unit</Tag>}
                </div>
                <p className="card-sub">
                  <span className="mono nrp">{k.nrp || "NRP belum diisi"}</span> · {k.statusKerja} · masuk{" "}
                  {fmtDate(k.tglMasuk)} · {k.hp || "kontak belum diisi"}
                </p>

                <div className="mini-grid">
                  <div>
                    <span className="mini-k">Gaji pokok</span>
                    <span className="mini-v mono">{rpShort(k.gajiPokok)}</span>
                  </div>
                  <div>
                    <span className="mini-k">Tunjangan</span>
                    <span className="mini-v mono">{rpShort(k.tunjangan)}</span>
                  </div>
                  <div>
                    <span className="mini-k">Total / bulan</span>
                    <span className="mini-v mono">{rpShort(totalGaji(k))}</span>
                  </div>
                </div>

                <div className="sim-row">
                  <span className="mini-k">{k.jenisSIM || "SIM tidak dicatat"}</span>
                  {st ? (
                    <Tag tone={st.level === "lewat" ? "bahaya" : st.level === "segera" ? "waspada" : "aman"}>
                      {st.level === "lewat"
                        ? `Habis ${-st.sisaHari} hari lalu`
                        : st.level === "segera"
                        ? `${st.sisaHari} hari lagi`
                        : `Berlaku s/d ${fmtDate(k.simExp)}`}
                    </Tag>
                  ) : (
                    <span className="muted small">—</span>
                  )}
                </div>

                <div className="card-actions">
                  <button className="btn btn-ghost sm act-ubah" onClick={() => onEdit(k)}>
                    Ubah
                  </button>
                  <button className="btn btn-danger sm" onClick={() => onDelete(k)}>
                    Hapus
                  </button>
                </div>
              </article>
            );
          })}
        </>
      )}
    </>
  );
}

function MasterData(props) {
  const { data, unitById, sub, setSub, user } = props;
  const tabs = [
    ["unit", "Unit", "m_unit"],
    ["karyawan", "Karyawan", "m_karyawan"],
    ["merek", "Merek", "m_merek"],
    ["asuransi", "Asuransi", "m_asuransi"],
    ["pinjaman", "Pinjaman", "m_pinjaman"],
    ["aktivitas", "Aktivitas", "aktivitas"],
  ].filter(([, , hak]) => boleh(user, hak));
  if (user && user.peran === "superadmin") {
    tabs.push(["pengguna", "Pengguna"]);
    tabs.push(["hak", "Hak akses"]);
  }
  const aktif = tabs.some(([k]) => k === sub) ? sub : (tabs[0] || ["unit"])[0];
  return (
    <>
      <div className="subnav">
        {tabs.map(([k, label]) => (
          <button key={k} className={aktif === k ? "sub-on" : ""} onClick={() => setSub(k)}>
            {label}
          </button>
        ))}
      </div>

      {aktif === "unit" && (
        <MasterUnit
          data={data}
          onAdd={props.onAddUnit}
          onEdit={props.onEditUnit}
          onDelete={props.onDelUnit}
        />
      )}

      {aktif === "karyawan" && (
        <KaryawanList
          data={data}
          unitById={unitById}
          onAdd={props.onAddKaryawan}
          onEdit={props.onEditKaryawan}
          onDelete={props.onDelKaryawan}
          onImport={props.onImportKaryawan}
          onSeed={props.onSeedKaryawan}
        />
      )}

      {aktif === "merek" && (
        <MasterMerek data={data} onAdd={props.onAddMerek} onEdit={props.onEditMerek} onDelete={props.onDelMerek} />
      )}

      {aktif === "asuransi" && (
        <MasterAsuransi
          data={data}
          onAdd={props.onAddAsuransi}
          onEdit={props.onEditAsuransi}
          onDelete={props.onDelAsuransi}
        />
      )}

      {aktif === "pengguna" && user && user.peran === "superadmin" && <PenggunaList user={user} data={data} />}

      {aktif === "hak" && user && user.peran === "superadmin" && <HakAkses />}

      {aktif === "aktivitas" && <Aktivitas />}

      {aktif === "pinjaman" && (
        <MasterPinjaman
          data={data}
          unitById={unitById}
          onAdd={props.onAddLoan}
          onEdit={props.onEditLoan}
          onDelete={props.onDelLoan}
        />
      )}
    </>
  );
}

function MasterUnit({ data, onAdd, onEdit, onDelete }) {
  const list = data.units.slice().sort((a, b) => a.kode.localeCompare(b.kode));
  return (
    <>
      <div className="toolbar">
        <h2>Daftar unit</h2>
        <button className="btn btn-primary sm" onClick={onAdd}>
          + Unit
        </button>
      </div>
      {list.length === 0 ? (
        <div className="panel">
          <Empty title="Belum ada unit terdaftar." action="Tambah unit" onAction={onAdd} />
        </div>
      ) : (
        list.map((u) => {
          const pol = (data.asuransi || []).find((a) => a.id === u.asuransiId);
          const st = asuransiStatus(pol);
          const loan = data.loans.find((l) => l.unitId === u.id);
          return (
            <article className="card" key={u.id}>
              <div className="card-head">
                <div>
                  <span className="unit-kode">{u.kode}</span>
                  <span className={`chip ${u.tipe === "Bus" ? "chip-bus" : "chip-lv"}`}>{u.tipe}</span>
                </div>
                <Plate>{u.nopol || "—"}</Plate>
              </div>
              <dl className="spec">
                <div>
                  <dt>Nomor lambung</dt>
                  <dd className="mono">{u.noLambung || "—"}</dd>
                </div>
                <div>
                  <dt>Merek / tipe</dt>
                  <dd>{[u.merek, u.model].filter(Boolean).join(" ") || "—"}</dd>
                </div>
                <div>
                  <dt>Tahun</dt>
                  <dd className="mono">{u.tahun || "—"}</dd>
                </div>
                <div>
                  <dt>Kode pinjaman</dt>
                  <dd className="mono">{loan ? loan.kode || loan.lembaga : "—"}</dd>
                </div>
                <div>
                  <dt>Asuransi</dt>
                  <dd>
                    {pol ? `${pol.kode || pol.nomorPolis} · ${pol.jenis}` : "—"}
                    {st && st.level !== "aman" ? (
                      <Tag tone={st.level === "lewat" ? "bahaya" : "waspada"}>
                        {st.level === "lewat" ? "Habis" : `${st.sisaHari} hari`}
                      </Tag>
                    ) : null}
                  </dd>
                </div>
              </dl>
              <div className="card-actions">
                <button className="btn btn-ghost sm act-ubah" onClick={() => onEdit(u)}>
                  Ubah
                </button>
                <button className="btn btn-danger sm" onClick={() => onDelete(u)}>
                  Hapus
                </button>
              </div>
            </article>
          );
        })
      )}
    </>
  );
}

function MasterMerek({ data, onAdd, onEdit, onDelete }) {
  const list = (data.merek || []).slice().sort((a, b) => a.nama.localeCompare(b.nama));
  return (
    <>
      <div className="toolbar">
        <h2>Merek kendaraan</h2>
        <button className="btn btn-primary sm" onClick={onAdd}>
          + Merek
        </button>
      </div>
      {list.length === 0 ? (
        <div className="panel">
          <Empty
            title="Belum ada merek. Merek yang didaftarkan di sini muncul sebagai pilihan saat menambah unit."
            action="Tambah merek"
            onAction={onAdd}
          />
        </div>
      ) : (
        list.map((m) => {
          const dipakai = data.units.filter((u) => u.merek === m.nama).length;
          return (
            <article className="row-card" key={m.id}>
              <div className="row-main">
                <div className="row-top">
                  <span className="unit-kode small">{m.nama}</span>
                  <span className="chip chip-plain">{m.tipe}</span>
                </div>
                <p className="row-desc">{m.keterangan || "—"}</p>
                <p className="muted small">
                  {m.agen || "agen belum diisi"} · dipakai {dipakai} unit
                </p>
              </div>
              <div className="row-side">
                <button className="link-del act-ubah" onClick={() => onEdit(m)}>
                  ubah
                </button>
                <button className="link-del act-hapus" onClick={() => onDelete(m)}>
                  hapus
                </button>
              </div>
            </article>
          );
        })
      )}
    </>
  );
}

function MasterAsuransi({ data, onAdd, onEdit, onDelete }) {
  const list = (data.asuransi || []).slice().sort((a, b) => (a.berakhir || "").localeCompare(b.berakhir || ""));
  const totalPremi = list.reduce((a, x) => a + (Number(x.premi) || 0), 0);
  return (
    <>
      <div className="toolbar">
        <h2>Polis asuransi</h2>
        <button className="btn btn-primary sm" onClick={onAdd}>
          + Polis
        </button>
      </div>
      {list.length === 0 ? (
        <div className="panel">
          <Empty title="Belum ada polis terdaftar." action="Tambah polis" onAction={onAdd} />
        </div>
      ) : (
        <>
          <div className="panel tight">
            <div className="totline">
              <span>{list.length} polis · premi per tahun</span>
              <strong className="mono neg">{rp(totalPremi)}</strong>
            </div>
          </div>
          {list.map((a) => {
            const st = asuransiStatus(a);
            const unit = data.units.find((u) => u.asuransiId === a.id);
            return (
              <article className="card" key={a.id}>
                <div className="card-head">
                  <div>
                    <span className="part-nama">{a.kode || a.nomorPolis}</span>
                    <span className="chip chip-plain">{a.jenis}</span>
                  </div>
                  {st ? (
                    <Tag tone={st.level === "lewat" ? "bahaya" : st.level === "segera" ? "waspada" : "aman"}>
                      {st.level === "lewat"
                        ? `Habis ${-st.sisaHari} hari lalu`
                        : st.level === "segera"
                        ? `${st.sisaHari} hari lagi`
                        : `s/d ${fmtDate(a.berakhir)}`}
                    </Tag>
                  ) : (
                    <Tag tone="netral">Tanpa masa berlaku</Tag>
                  )}
                </div>
                <p className="card-sub">
                  {a.penanggung || "penanggung belum diisi"} · polis {a.nomorPolis || "—"}
                </p>
                <dl className="spec">
                  <div>
                    <dt>Unit</dt>
                    <dd className="mono">{unit ? unit.kode : "belum dipasangkan"}</dd>
                  </div>
                  <div>
                    <dt>Premi</dt>
                    <dd className="mono">{a.premi ? rp(a.premi) : "—"}</dd>
                  </div>
                  <div>
                    <dt>Periode</dt>
                    <dd>
                      {fmtDate(a.mulai)} — {fmtDate(a.berakhir)}
                    </dd>
                  </div>
                </dl>
                {a.keterangan ? <p className="muted small">{a.keterangan}</p> : null}
                <div className="card-actions">
                  <button className="btn btn-ghost sm act-ubah" onClick={() => onEdit(a)}>
                    Ubah
                  </button>
                  <button className="btn btn-danger sm" onClick={() => onDelete(a)}>
                    Hapus
                  </button>
                </div>
              </article>
            );
          })}
        </>
      )}
    </>
  );
}

function MasterPinjaman({ data, unitById, onAdd, onEdit, onDelete }) {
  const list = data.loans.slice();
  return (
    <>
      <div className="toolbar">
        <h2>Kode pinjaman</h2>
        <button className="btn btn-primary sm" onClick={onAdd}>
          + Pinjaman
        </button>
      </div>
      {list.length === 0 ? (
        <div className="panel">
          <Empty
            title="Belum ada kontrak pembiayaan. Kode yang didaftarkan di sini bisa dipilih dari formulir unit."
            action="Tambah pinjaman"
            onAction={onAdd}
          />
        </div>
      ) : (
        list.map((l) => {
          const st = loanStatus(l);
          const u = l.unitId ? unitById[l.unitId] : null;
          return (
            <article className="card" key={l.id}>
              <div className="card-head">
                <div>
                  <span className="part-nama">{l.kode || "Tanpa kode"}</span>
                </div>
                {u ? <Plate>{u.kode}</Plate> : <Tag tone="netral">Tanpa unit</Tag>}
              </div>
              <p className="card-sub">{l.lembaga || "lembaga belum diisi"}</p>
              <dl className="spec">
                <div>
                  <dt>Pokok</dt>
                  <dd className="mono">{rp(l.pokok)}</dd>
                </div>
                <div>
                  <dt>Angsuran</dt>
                  <dd className="mono">{rp(l.angsuran)} / bulan</dd>
                </div>
                <div>
                  <dt>Tenor</dt>
                  <dd className="mono">
                    {l.tenor} bulan · terbayar {st.paid}
                  </dd>
                </div>
                <div>
                  <dt>Jatuh tempo</dt>
                  <dd className="mono">tiap tanggal {l.tglJatuhTempo}</dd>
                </div>
              </dl>
              <div className="card-actions">
                <button className="btn btn-ghost sm act-ubah" onClick={() => onEdit(l)}>
                  Ubah
                </button>
                <button className="btn btn-danger sm" onClick={() => onDelete(l)}>
                  Hapus
                </button>
              </div>
            </article>
          );
        })
      )}
    </>
  );
}

function PenggunaList({ user, data }) {
  const PERAN = [
    ["superadmin", "Super Admin"],
    ["admin", "Admin"],
    ["staff", "Staff"],
  ];
  const labelPeran = (p) => (PERAN.find((x) => x[0] === p) || [p, p])[1];

  const [list, setList] = useState([]);
  const [muat, setMuat] = useState(true);
  const [pesan, setPesan] = useState("");
  const [galat, setGalat] = useState("");
  const [form, setForm] = useState(null);
  const [sandiku, setSandiku] = useState(null);

  const ambil = async () => {
    try {
      setList(await api("users"));
      setGalat("");
    } catch (e) {
      setGalat(e.message);
    }
    setMuat(false);
  };

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

  const simpan = async () => {
    try {
      await api("user_save", form);
      setForm(null);
      setPesan("Pengguna tersimpan.");
      ambil();
    } catch (e) {
      setGalat(e.message);
    }
  };

  const buang = async (u) => {
    try {
      await api("user_delete", { id: u.id });
      setPesan(`Pengguna ${u.username} dihapus.`);
      ambil();
    } catch (e) {
      setGalat(e.message);
    }
  };

  const gantiSandi = async () => {
    try {
      await api("ganti_sandi", sandiku);
      setSandiku(null);
      setPesan("Kata sandi diperbarui.");
    } catch (e) {
      setGalat(e.message);
    }
  };

  const lemah = form && form.password && form.password.length < 6;

  return (
    <>
      <div className="toolbar">
        <h2>Akun aplikasi</h2>
        <div className="tool-btns">
          <button className="btn btn-ghost sm" onClick={() => setSandiku({ lama: "", baru: "" })}>
            Sandi saya
          </button>
          <button
            className="btn btn-primary sm"
            onClick={() => setForm({ peran: "admin", password: SANDI_BAWAAN.admin, wajib_ganti: true })}
          >
            + Pengguna
          </button>
        </div>
      </div>

      {galat ? <div className="banner-err">{galat}</div> : null}
      {pesan ? <div className="banner-ok">{pesan}</div> : null}

      <div className="panel tight">
        <p className="muted small">
          Setiap orang masuk memakai NRP-nya. Pilih dari data kru agar NRP dan namanya sama persis dengan Master →
          Karyawan.
        </p>
      </div>

      {muat ? (
        <div className="panel">
          <p className="muted">Memuat daftar akun…</p>
        </div>
      ) : (
        list.map((u) => (
          <article className="row-card" key={u.id}>
            <div className="row-main">
              <div className="row-top">
                <span className="mono nrp">{u.username}</span>
                <span className={`chip chip-${u.peran === "superadmin" ? "warn" : "plain"}`}>
                  {labelPeran(u.peran)}
                </span>
              </div>
              <p className="row-desc">
                {u.nama}
                {(data.karyawan || []).some((k) => (k.nrp || "").toUpperCase() === (u.username || "").toUpperCase())
                  ? ""
                  : " · NRP di luar data kru"}
              </p>
              <p className="muted small">
                dibuat {fmtDate((u.dibuat || "").slice(0, 10))}
                {u.username === user.username ? " · akun yang sedang dipakai" : ""}
                {Number(u.wajib_ganti) ? " · masih memakai sandi bawaan" : ""}
              </p>
            </div>
            <div className="row-side">
              <button
                className="link-del act-ubah"
                onClick={() => setForm({ id: u.id, username: u.username, nama: u.nama, peran: u.peran })}
              >
                ubah
              </button>
              {u.username === user.username ? null : (
                <button className="link-del act-hapus" onClick={() => buang(u)}>
                  hapus
                </button>
              )}
            </div>
          </article>
        ))
      )}

      <div className="panel">
        <h2>Arti peran</h2>
        <dl className="spec">
          <div>
            <dt>Super Admin</dt>
            <dd>Semua data + kelola pengguna</dd>
          </div>
          <div>
            <dt>Admin</dt>
            <dd>Petugas input: isi &amp; ubah seluruh data</dd>
          </div>
          <div>
            <dt>Staff</dt>
            <dd>Pengawas: lihat semua + catatan aktivitas</dd>
          </div>
        </dl>
      </div>

      <Sheet
        open={!!form}
        title={form && form.id ? "Ubah pengguna" : "Pengguna baru"}
        onClose={() => setForm(null)}
        footer={
          <>
            <button className="btn btn-ghost" onClick={() => setForm(null)}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={simpan}>
              Simpan pengguna
            </button>
          </>
        }
      >
        {form ? (
          <>
            <Field label="Ambil dari data kru" hint="opsional">
              <select
                value=""
                onChange={(e) => {
                  const k = (data.karyawan || []).find((x) => x.id === e.target.value);
                  if (k) setForm({ ...form, username: k.nrp || "", nama: k.nama });
                }}
              >
                <option value="">— Isi manual —</option>
                {(data.karyawan || [])
                  .filter((k) => k.nrp)
                  .map((k) => (
                    <option key={k.id} value={k.id}>
                      {k.nrp} — {k.nama} ({k.jabatan})
                    </option>
                  ))}
              </select>
            </Field>
            <div className="row2">
              <Field label="NRP" hint="dipakai untuk masuk">
                <input
                  value={form.username || ""}
                  onChange={(e) => setForm({ ...form, username: e.target.value })}
                  autoComplete="off"
                  placeholder="NRP-0004"
                />
              </Field>
              <Field label="Nama lengkap">
                <input value={form.nama || ""} onChange={(e) => setForm({ ...form, nama: e.target.value })} />
              </Field>
            </div>
            <Field label="Peran">
              <select
                value={form.peran || "admin"}
                onChange={(e) => {
                  const baru = e.target.value;
                  const isiLama = form.password || "";
                  const otomatis = isiLama === "" || Object.values(SANDI_BAWAAN).includes(isiLama);
                  setForm({
                    ...form,
                    peran: baru,
                    password: form.id ? form.password : otomatis ? SANDI_BAWAAN[baru] || "" : isiLama,
                  });
                }}
              >
                {PERAN.map(([v, l]) => (
                  <option key={v} value={v}>
                    {l}
                  </option>
                ))}
              </select>
            </Field>
            <Field
              label="Kata sandi"
              hint={form.id ? "kosongkan bila tidak diubah" : "bawaan terisi otomatis"}
            >
              <input
                type="password"
                value={form.password || ""}
                onChange={(e) => setForm({ ...form, password: e.target.value })}
                autoComplete="new-password"
              />
            </Field>
            <label className="hak-row kotak-centang">
              <input
                type="checkbox"
                checked={!!form.wajib_ganti}
                onChange={(e) => setForm({ ...form, wajib_ganti: e.target.checked })}
              />
              <span>Minta ganti sandi saat pertama masuk</span>
            </label>
            {lemah && !form.wajib_ganti ? (
              <p className="warn-line">
                Sandi sependek ini bisa ditebak dalam hitungan detik oleh robot yang memindai internet. Centang
                pilihan di atas agar penggunanya wajib menggantinya begitu masuk.
              </p>
            ) : null}
          </>
        ) : null}
      </Sheet>

      <Sheet
        open={!!sandiku}
        title="Ganti kata sandi saya"
        onClose={() => setSandiku(null)}
        footer={
          <>
            <button className="btn btn-ghost" onClick={() => setSandiku(null)}>
              Batal
            </button>
            <button className="btn btn-primary" onClick={gantiSandi}>
              Simpan sandi
            </button>
          </>
        }
      >
        {sandiku ? (
          <>
            <Field label="Kata sandi lama">
              <input
                type="password"
                value={sandiku.lama}
                onChange={(e) => setSandiku({ ...sandiku, lama: e.target.value })}
                autoComplete="current-password"
              />
            </Field>
            <Field label="Kata sandi baru">
              <input
                type="password"
                value={sandiku.baru}
                onChange={(e) => setSandiku({ ...sandiku, baru: e.target.value })}
                autoComplete="new-password"
              />
            </Field>
          </>
        ) : null}
      </Sheet>
    </>
  );
}

function HakAkses() {
  const KELOMPOK = [
    ["Menu utama", ["ringkas", "unit", "servis", "part", "keuangan", "master"]],
    ["Isi menu Master", ["m_unit", "m_karyawan", "m_merek", "m_asuransi", "m_pinjaman", "aktivitas"]],
    ["Wewenang data", ["tambah", "hapus"]],
  ];
  const PERAN = [
    ["admin", "Admin"],
    ["staff", "Staff"],
  ];

  const [peran, setPeran] = useState("admin");
  const [semua, setSemua] = useState({});
  const [muat, setMuat] = useState(true);
  const [galat, setGalat] = useState("");
  const [pesan, setPesan] = useState("");

  const ambil = async () => {
    try {
      const d = await api("hak");
      setSemua(d.peran);
      setGalat("");
    } catch (e) {
      setGalat(e.message);
    }
    setMuat(false);
  };

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

  const aktif = semua[peran] || [];

  const alih = (k) => {
    const baru = aktif.includes(k) ? aktif.filter((x) => x !== k) : [...aktif, k];
    setSemua({ ...semua, [peran]: baru });
    setPesan("");
  };

  const simpan = async () => {
    try {
      await api("hak_save", { peran, hak: aktif });
      setPesan(`Hak akses ${peran === "admin" ? "Admin" : "Staff"} tersimpan. Berlaku saat orangnya masuk lagi.`);
      setGalat("");
    } catch (e) {
      setGalat(e.message);
    }
  };

  return (
    <>
      <div className="toolbar">
        <h2>Hak akses peran</h2>
      </div>

      <div className="panel tight">
        <p className="muted small">
          Super Admin selalu memegang seluruh menu dan wewenang, termasuk halaman ini, dan tidak bisa dikurangi.
          Atur peran lain di bawah.
        </p>
      </div>

      <div className="seg seg-wide">
        {PERAN.map(([v, l]) => (
          <button key={v} className={peran === v ? "seg-on" : ""} onClick={() => setPeran(v)}>
            {l}
          </button>
        ))}
      </div>

      {galat ? <div className="banner-err">{galat}</div> : null}
      {pesan ? <div className="banner-ok">{pesan}</div> : null}

      {muat ? (
        <div className="panel">
          <p className="muted">Memuat pengaturan…</p>
        </div>
      ) : (
        <>
          {KELOMPOK.map(([judul, keys]) => (
            <div className="hak-grid" key={judul}>
              <div className="hak-kel">{judul}</div>
              {keys.map((k) => (
                <label className="hak-row" key={k}>
                  <input type="checkbox" checked={aktif.includes(k)} onChange={() => alih(k)} />
                  <span>{NAMA_HAK[k]}</span>
                </label>
              ))}
            </div>
          ))}
          <p className="muted small nudge">
            Mencabut &quot;Tambah &amp; ubah&quot; sekaligus &quot;Hapus&quot; membuat peran itu hanya bisa melihat —
            server akan menolak setiap penyimpanan darinya.
          </p>
          <button className="btn btn-primary full" onClick={simpan}>
            Simpan hak akses
          </button>
        </>
      )}
    </>
  );
}

function Aktivitas() {
  const [list, setList] = useState([]);
  const [muat, setMuat] = useState(true);
  const [galat, setGalat] = useState("");

  const ambil = async () => {
    setMuat(true);
    try {
      setList(await api("log"));
      setGalat("");
    } catch (e) {
      setGalat(e.message);
    }
    setMuat(false);
  };

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

  const jam = (w) => {
    if (!w) return "—";
    const dt = new Date(w.replace(" ", "T"));
    if (isNaN(dt)) return w;
    return dt.toLocaleString("id-ID", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" });
  };

  return (
    <>
      <div className="toolbar">
        <h2>Catatan aktivitas</h2>
        <button className="btn btn-ghost sm" onClick={ambil}>
          Muat ulang
        </button>
      </div>

      {galat ? <div className="banner-err">{galat}</div> : null}

      {muat ? (
        <div className="panel">
          <p className="muted">Memuat catatan…</p>
        </div>
      ) : list.length === 0 ? (
        <div className="panel">
          <Empty title="Belum ada perubahan data yang tercatat." />
        </div>
      ) : (
        <>
          <div className="panel tight">
            <div className="totline">
              <span>{list.length} perubahan terakhir</span>
            </div>
          </div>
          {list.map((l, i) => (
            <article className="row-card" key={i}>
              <div className="row-main">
                <div className="row-top">
                  <span className="unit-kode small">{l.username}</span>
                  <span className={`chip chip-${l.peran === "admin" ? "warn" : "plain"}`}>{l.peran}</span>
                  <span className="muted small">{jam(l.waktu)}</span>
                </div>
                <p className="row-desc">{l.ringkasan}</p>
              </div>
            </article>
          ))}
        </>
      )}
    </>
  );
}

/* ============================== ICONS ============================== */

function TabIcon({ name }) {
  const c = { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round" };
  if (name === "dash")
    return (
      <svg {...c}>
        <path d="M3 13a9 9 0 0 1 18 0" />
        <path d="M12 13l4-3" />
        <path d="M3 13v5h18v-5" />
      </svg>
    );
  if (name === "unit")
    return (
      <svg {...c}>
        <path d="M3 16v-4l2-5h14l2 5v4" />
        <path d="M3 16h18v3h-3v-3M6 19H3v-3" />
        <circle cx="7.5" cy="16" r="1.5" />
        <circle cx="16.5" cy="16" r="1.5" />
      </svg>
    );
  if (name === "servis")
    return (
      <svg {...c}>
        <path d="M14.7 6.3a4 4 0 0 1-5.4 5.4L4 17l3 3 5.3-5.3a4 4 0 0 0 5.4-5.4l-2.2 2.2-2.1-2.1z" />
      </svg>
    );
  if (name === "master")
    return (
      <svg {...c}>
        <rect x="3.5" y="4" width="17" height="5" rx="1" />
        <rect x="3.5" y="11" width="17" height="5" rx="1" />
        <path d="M7 6.5h.01M7 13.5h.01" />
        <path d="M6 18.5h12" />
      </svg>
    );
  if (name === "kru")
    return (
      <svg {...c}>
        <circle cx="9" cy="8" r="3.2" />
        <path d="M3.5 19a5.5 5.5 0 0 1 11 0" />
        <path d="M16 6.2a3 3 0 0 1 0 5.6" />
        <path d="M17.5 14.2A5.2 5.2 0 0 1 20.5 19" />
      </svg>
    );
  if (name === "part")
    return (
      <svg {...c}>
        <circle cx="12" cy="12" r="8" />
        <circle cx="12" cy="12" r="3" />
        <path d="M12 4v3M12 17v3M4 12h3M17 12h3" />
      </svg>
    );
  return (
    <svg {...c}>
      <rect x="3" y="6" width="18" height="12" rx="2" />
      <path d="M3 10h18" />
      <path d="M7 15h4" />
    </svg>
  );
}

/* ============================== STYLE ============================== */

function Style() {
  return (
    <style>{`
@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@500;600;700&family=IBM+Plex+Mono:wght@500;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap');

.app{
  --ground:#E9EBEE;
  --panel:#FFFFFF;
  --ink:#16202B;
  --ink-2:#5A6472;
  --line:#D3D7DC;
  --hazard:#F26522;
  --diesel:#1F7A5A;
  --amber:#C98A00;
  --steel:#2C3D52;
  font-family:'IBM Plex Sans',system-ui,-apple-system,sans-serif;
  background:var(--ground);
  color:var(--ink);
  min-height:100vh;
  padding-bottom:78px;
  -webkit-font-smoothing:antialiased;
}
.app *{box-sizing:border-box}
.app h1,.app h2,.app h3{font-family:'Barlow Condensed','Arial Narrow',sans-serif;letter-spacing:.01em;margin:0}
.mono{font-family:'IBM Plex Mono',ui-monospace,monospace;font-variant-numeric:tabular-nums}
.small{font-size:11px}
.muted{color:var(--ink-2)}
.pos{color:var(--diesel)}
.neg{color:var(--hazard)}
.loading{padding:40px 20px;color:var(--ink-2);font-size:14px}

/* topbar */
.topbar{
  position:sticky;top:0;z-index:20;
  background:var(--steel);color:#fff;
  padding:14px 16px 12px;
  border-bottom:3px solid var(--hazard);
}
.topbar h1{font-size:26px;font-weight:600;text-transform:uppercase;line-height:1}
.eyebrow{margin:0 0 2px;font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:#93A3B5}
.seg{display:flex;gap:0;margin-top:12px;border:1px solid rgba(255,255,255,.25);border-radius:2px;overflow:hidden}
.seg button{flex:1;background:transparent;border:0;color:#C3CDD8;padding:7px 0;font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;font-family:inherit}
.seg .seg-on{background:var(--hazard);color:#fff}
.seg-wide{margin:12px 12px 0;border-color:var(--line);border-radius:2px}
.seg-wide button{color:var(--ink-2);background:var(--panel)}
.seg-wide .seg-on{background:var(--steel);color:#fff}

.main{padding:12px}

/* panel & card */
.panel{background:var(--panel);border:1px solid var(--line);border-radius:3px;padding:14px;margin-bottom:12px}
.panel.tight{padding:10px 14px}
.panel h2{font-size:16px;text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px}
.toolbar{display:flex;align-items:center;justify-content:space-between;margin:2px 2px 10px}
.toolbar h2{font-size:17px;text-transform:uppercase;letter-spacing:.04em}
.card{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--steel);border-radius:3px;padding:13px;margin-bottom:10px}
.card-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
.card-sub{margin:5px 0 10px;font-size:12px;color:var(--ink-2)}
.card-actions{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:11px}
.unit-kode{font-family:'Barlow Condensed',sans-serif;font-size:22px;font-weight:700;letter-spacing:.02em}
.unit-kode.small{font-size:16px}
.part-nama{font-family:'Barlow Condensed',sans-serif;font-size:20px;font-weight:600}
.chip{display:inline-block;margin-left:7px;padding:2px 7px;border-radius:2px;font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;vertical-align:middle}
.chip-lv{background:#E4EDF5;color:#2C3D52}
.chip-bus{background:#E2F0EA;color:#1F7A5A}
.chip-plain{background:#EFF1F3;color:var(--ink-2);margin-left:0}
.chip-warn{background:#FCE9DE;color:#B24413;margin-left:0}

/* plate */
.plate{
  font-family:'Barlow Condensed',sans-serif;font-weight:700;font-size:15px;letter-spacing:.06em;
  background:#1A1A1A;color:#F5F5F5;padding:3px 9px;border-radius:2px;border:2px solid #F5F5F5;
  box-shadow:0 0 0 1px #1A1A1A;white-space:nowrap;
}

/* stats */
.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line)}
.stat{background:var(--panel);padding:11px 12px;display:flex;flex-direction:column;gap:3px}
.stat-k{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2)}
.stat-v{font-size:16px;font-weight:600}
.netto{display:flex;align-items:center;justify-content:space-between;margin-top:12px;padding:11px 12px;border-radius:2px;font-size:12px;letter-spacing:.06em;text-transform:uppercase}
.netto strong{font-size:17px;text-transform:none;letter-spacing:0}
.netto-pos{background:#E7F2ED;color:#155C43}
.netto-neg{background:#FCEAE1;color:#A63C0F}

/* alerts */
.alerts{list-style:none;margin:0;padding:0}
.alerts li{display:flex;align-items:center;gap:8px;padding:9px 0;border-bottom:1px dashed var(--line);cursor:pointer}
.alerts li:last-child{border-bottom:0}
.alert-txt{flex:1;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tag{font-size:9px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;padding:3px 6px;border-radius:2px;white-space:nowrap}
.tag-bahaya{background:var(--hazard);color:#fff}
.tag-waspada{background:#FFF0CC;color:#8A5D00}
.tag-aman{background:#E2F0EA;color:#1F7A5A}
.tag-netral{background:#EFF1F3;color:var(--ink-2)}

/* rank */
.rank{list-style:none;margin:0;padding:0}
.rank li{display:flex;align-items:center;gap:9px;padding:6px 0}
.rank-k{font-family:'Barlow Condensed',sans-serif;font-weight:600;font-size:14px;width:58px}
.rank-bar{flex:1;height:9px;background:#EFF1F3;border-radius:1px;overflow:hidden}
.rank-bar i{display:block;height:100%;background:var(--hazard)}
.chart{margin:0 -6px}

/* odometer */
.odo-row{display:flex;align-items:baseline;gap:6px;background:#111820;color:#EAF0F6;padding:8px 11px;border-radius:2px;cursor:pointer}
.odo-label{font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:#7E8C9C}
.odo-val{flex:1;text-align:right;font-size:19px;font-weight:600;letter-spacing:.05em}
.odo-unit{font-size:10px;color:#7E8C9C}
.odo-edit{font-size:10px;color:var(--hazard);text-transform:uppercase;letter-spacing:.08em}

.mini-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:10px 0 8px}
.mini-k{display:block;font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2)}
.mini-v{display:block;font-size:14px;font-weight:600;margin-top:2px}

/* wear gauge (signature) */
.wear{position:relative;height:16px;background:#EDEFF2;border:1px solid var(--line);border-radius:2px;overflow:hidden;margin-top:4px}
.wear-fill{height:100%;transition:width .3s ease}
.wear-aman{background:linear-gradient(90deg,#2E9B73,#1F7A5A)}
.wear-segera{background:linear-gradient(90deg,#E8B400,#C98A00)}
.wear-lewat{background:linear-gradient(90deg,#F26522,#C43D07)}
.wear-ticks{position:absolute;inset:0;background-image:repeating-linear-gradient(90deg,transparent 0 9px,rgba(22,32,43,.16) 9px 10px)}
.wear-legend{display:flex;justify-content:space-between;gap:6px;margin-top:6px;color:var(--ink-2)}
.progress{height:8px;background:#EDEFF2;border:1px solid var(--line);border-radius:2px;overflow:hidden;margin-top:6px}
.progress-fill{height:100%;background:var(--steel)}

/* rows */
.row-card{display:flex;gap:10px;background:var(--panel);border:1px solid var(--line);border-radius:3px;padding:11px 12px;margin-bottom:8px}
.row-main{flex:1;min-width:0}
.row-top{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.row-desc{margin:5px 0 3px;font-size:13px}
.row-side{display:flex;flex-direction:column;align-items:flex-end;gap:5px;font-size:13px;font-weight:600;white-space:nowrap}
.link-del{background:none;border:0;color:var(--ink-2);font-size:10px;text-transform:uppercase;letter-spacing:.08em;cursor:pointer;padding:0;font-family:inherit}
.link-del:hover{color:var(--hazard)}
.totline{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:var(--ink-2)}
.totline strong{font-size:15px;color:var(--ink)}

/* buttons */
.btn{font-family:'Barlow Condensed',sans-serif;font-size:15px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:9px 14px;border-radius:2px;border:1px solid transparent;cursor:pointer}
.btn.sm{font-size:13px;padding:6px 10px}
.btn.full{width:100%;margin-top:8px}
.btn-primary{background:var(--hazard);color:#fff}
.btn-primary:disabled{background:#D8DCE1;color:#9AA3AE;cursor:not-allowed}
.btn-ghost{background:transparent;border-color:var(--line);color:var(--ink)}
.btn-ghost:disabled{color:#B4BBC3;cursor:not-allowed}
.btn-danger{background:transparent;border-color:#E7C7BA;color:#B24413}
.icon-btn{background:none;border:0;font-size:16px;color:var(--ink-2);cursor:pointer;padding:4px 6px}

/* empty */
.empty{text-align:center;padding:14px 4px}
.empty p{margin:0 0 12px;font-size:13px;color:var(--ink-2);line-height:1.5}

/* tabbar */
.tabbar{position:fixed;left:0;right:0;bottom:0;display:flex;background:var(--panel);border-top:1px solid var(--line);z-index:30}
.tabbar button{flex:1;background:none;border:0;padding:8px 0 10px;display:flex;flex-direction:column;align-items:center;gap:3px;color:var(--ink-2);cursor:pointer;font-family:inherit}
.tabbar span{font-size:9px;letter-spacing:.02em;text-transform:uppercase;font-weight:600}
.tabbar svg{width:19px;height:19px}
.stat-wide{grid-column:1/-1;flex-direction:row;align-items:baseline;justify-content:space-between;cursor:pointer}
.sim-row{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;background:#F4F6F8;border-radius:2px;margin-top:2px}
.tabbar .tab-on{color:var(--hazard);box-shadow:inset 0 3px 0 var(--hazard)}

/* sheet */
.sheet-scrim{position:fixed;inset:0;background:rgba(16,24,32,.55);display:flex;align-items:flex-end;justify-content:center;z-index:50}
.sheet{background:var(--panel);width:100%;max-width:520px;max-height:88vh;display:flex;flex-direction:column;border-radius:6px 6px 0 0;border-top:3px solid var(--hazard)}
.sheet-head{display:flex;align-items:center;justify-content:space-between;padding:13px 15px;border-bottom:1px solid var(--line)}
.sheet-head h3{font-size:19px;text-transform:uppercase;letter-spacing:.03em}
.sheet-body{padding:14px 15px;overflow-y:auto}
.sheet-foot{display:flex;gap:8px;padding:11px 15px;border-top:1px solid var(--line)}
.sheet-foot .btn{flex:1}
.field{display:block;margin-bottom:12px}
.field-label{display:flex;justify-content:space-between;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2);margin-bottom:5px}
.field-label em{font-style:normal;text-transform:none;letter-spacing:0;color:#98A1AC}
.field input,.field select,.field textarea{width:100%;padding:10px 11px;border:1px solid var(--line);border-radius:2px;font-size:15px;font-family:'IBM Plex Sans',sans-serif;background:#FBFCFD;color:var(--ink)}
.field textarea{font-family:'IBM Plex Mono',monospace;font-size:12px;line-height:1.6;resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus{outline:2px solid var(--hazard);outline-offset:-1px}
.hint-box{background:#F4F6F8;border-left:3px solid var(--steel);padding:10px 11px;margin:0 0 14px;font-size:12px;line-height:1.7;color:var(--ink-2)}
.tool-btns{display:flex;gap:6px}
.subnav{display:flex;gap:6px;overflow-x:auto;padding:2px 2px 10px;-webkit-overflow-scrolling:touch}
.subnav button{flex:0 0 auto;background:var(--panel);border:1px solid var(--line);border-radius:2px;padding:7px 12px;font-family:'Barlow Condensed',sans-serif;font-size:14px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-2);cursor:pointer}
.subnav .sub-on{background:var(--steel);border-color:var(--steel);color:#fff}
.spec{margin:8px 0 2px;padding:0;display:grid;gap:1px;background:var(--line);border:1px solid var(--line)}
.spec>div{background:var(--panel);display:flex;justify-content:space-between;align-items:baseline;gap:10px;padding:7px 10px}
.spec dt{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2)}
.spec dd{margin:0;font-size:13px;text-align:right;display:flex;align-items:center;gap:6px;justify-content:flex-end}
.nudge{margin:-6px 0 12px}
.app[data-tambah="0"] .btn-primary,
.app[data-tambah="0"] .act-ubah{display:none}
.app[data-hapus="0"] .btn-danger,
.app[data-hapus="0"] .act-hapus{display:none}
.hak-grid{display:grid;gap:1px;background:var(--line);border:1px solid var(--line);margin-bottom:12px}
.hak-row{background:var(--panel);display:flex;align-items:center;gap:10px;padding:9px 11px;cursor:pointer}
.hak-row input{width:17px;height:17px;accent-color:var(--hazard);flex:0 0 auto}
.hak-row span{font-size:13px}
.kotak-centang{border:1px solid var(--line);border-radius:2px;margin:-4px 0 12px}
.hak-kel{background:#F4F6F8;padding:6px 11px;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2)}
.nrp{background:#EFF1F3;padding:1px 6px;border-radius:2px;font-size:11px;letter-spacing:.04em;color:var(--ink)}
.top-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
.top-user{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}
.who{font-size:11px;color:#C3CDD8;letter-spacing:.04em}
.sync{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:#8FA0B2;border:1px solid rgba(255,255,255,.2);padding:2px 6px;border-radius:2px}
.sync-on{color:#fff;border-color:var(--hazard);background:var(--hazard)}
.keluar{color:#fff;border-color:rgba(255,255,255,.35);padding:4px 9px}
.banner-ok{background:#E7F2ED;border-left:3px solid var(--diesel);color:#155C43;padding:10px 12px;font-size:12px;margin-bottom:12px;border-radius:2px}
.warn-line{background:#FFF6DF;border-left:3px solid var(--amber);color:#7A5400;padding:9px 11px;font-size:12px;line-height:1.6;margin:-4px 0 12px;border-radius:2px}
.banner-err{background:#FCEAE1;border-left:3px solid var(--hazard);color:#A63C0F;padding:10px 12px;font-size:12px;margin-bottom:12px;border-radius:2px}
.mini-chart svg{width:100%;height:auto;display:block}
.ch-axis{font-size:7px;fill:#5A6472;font-family:'IBM Plex Mono',monospace}
.ch-legend{display:flex;gap:16px;justify-content:center;margin-top:8px;font-size:11px;color:var(--ink-2)}
.dot{display:inline-block;width:9px;height:9px;border-radius:1px;margin-right:5px;vertical-align:-1px}
.dot-in{background:#1F7A5A}
.dot-out{background:#F26522}
.login-wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;background:var(--steel)}
.login-card{background:var(--panel);border-radius:3px;border-top:3px solid var(--hazard);padding:22px 20px;width:100%;max-width:360px}
.login-h1{font-size:26px;text-transform:uppercase;margin-bottom:18px;line-height:1}
.eyebrow.dark{color:var(--ink-2)}
.login-err{background:#FCEAE1;color:#A63C0F;font-size:12px;padding:8px 10px;border-radius:2px;margin:0 0 10px}
.boot{padding:40px;color:var(--ink-2)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:10px}

.app button:focus-visible{outline:2px solid var(--hazard);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.app *{transition:none!important}}
@media(min-width:640px){
  .main{max-width:720px;margin:0 auto;padding:16px}
  .topbar{padding:16px 22px 14px}
  .stat-grid{grid-template-columns:repeat(4,1fr)}
  .stat-wide{grid-column:1/-1}
}
`}</style>
  );
}

/* ============================== CHART, LOGIN, ROOT ============================== */

function MiniChart({ rows }) {
  const W = 320, H = 148, padL = 30, padB = 18, padT = 8;
  const max = Math.max(1, ...rows.map((r) => Math.max(r.Pemasukan, r.Biaya)));
  const plotH = H - padB - padT;
  const slot = (W - padL) / Math.max(1, rows.length);
  const bw = Math.min(13, slot / 3);
  const y = (v) => padT + plotH - (v / max) * plotH;
  const garis = [0, 0.5, 1];
  return (
    <div className="mini-chart">
      <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label="Grafik pemasukan dan biaya enam bulan">
        {garis.map((g) => (
          <g key={g}>
            <line x1={padL} x2={W} y1={y(max * g)} y2={y(max * g)} stroke="#D3D7DC" strokeDasharray="2 4" />
            <text x={padL - 4} y={y(max * g) + 3} textAnchor="end" className="ch-axis">
              {g === 0 ? "0" : rpShort(max * g)}
            </text>
          </g>
        ))}
        {rows.map((r, i) => {
          const x0 = padL + i * slot + slot / 2;
          return (
            <g key={r.bulan}>
              <rect x={x0 - bw - 1} y={y(r.Pemasukan)} width={bw} height={Math.max(0, y(0) - y(r.Pemasukan))} fill="#1F7A5A" />
              <rect x={x0 + 1} y={y(r.Biaya)} width={bw} height={Math.max(0, y(0) - y(r.Biaya))} fill="#F26522" />
              <text x={x0} y={H - 5} textAnchor="middle" className="ch-axis">
                {r.bulan}
              </text>
            </g>
          );
        })}
      </svg>
      <div className="ch-legend">
        <span>
          <i className="dot dot-in" /> Pemasukan
        </span>
        <span>
          <i className="dot dot-out" /> Biaya perawatan
        </span>
      </div>
    </div>
  );
}

function Login({ onMasuk }) {
  const [u, setU] = useState("");
  const [p, setP] = useState("");
  const [err, setErr] = useState("");
  const [sibuk, setSibuk] = useState(false);

  const masuk = async () => {
    if (!u || !p) {
      setErr("NRP dan kata sandi harus diisi.");
      return;
    }
    setSibuk(true);
    setErr("");
    try {
      const user = await api("login", { username: u, password: p });
      onMasuk(user);
    } catch (e) {
      setErr(e.message === "401" ? "NRP atau kata sandi salah." : e.message);
      setSibuk(false);
    }
  };

  return (
    <div className="app">
      <Style />
      <div className="login-wrap">
        <div className="login-card">
          <p className="eyebrow dark">Buku servis armada</p>
          <h1 className="login-h1">Monitoring LV &amp; Bus</h1>
          <Field label="NRP">
            <input
              value={u}
              onChange={(e) => setU(e.target.value)}
              autoComplete="username"
              placeholder="NRP-0004"
            />
          </Field>
          <Field label="Kata sandi">
            <input
              type="password"
              value={p}
              onChange={(e) => setP(e.target.value)}
              autoComplete="current-password"
              onKeyDown={(e) => e.key === "Enter" && masuk()}
            />
          </Field>
          {err ? <p className="login-err">{err}</p> : null}
          <button className="btn btn-primary full" onClick={masuk} disabled={sibuk}>
            {sibuk ? "Memeriksa…" : "Masuk"}
          </button>
        </div>
      </div>
    </div>
  );
}

function WajibGanti({ user, onSelesai, onLogout }) {
  const [lama, setLama] = useState("");
  const [baru, setBaru] = useState("");
  const [ulang, setUlang] = useState("");
  const [err, setErr] = useState("");
  const [sibuk, setSibuk] = useState(false);

  const simpan = async () => {
    if (baru.length < 4) {
      setErr("Kata sandi baru minimal 4 karakter.");
      return;
    }
    if (baru !== ulang) {
      setErr("Ulangi kata sandi belum sama.");
      return;
    }
    setSibuk(true);
    setErr("");
    try {
      await api("ganti_sandi", { lama, baru });
      onSelesai();
    } catch (e) {
      setErr(e.message);
      setSibuk(false);
    }
  };

  return (
    <div className="app">
      <Style />
      <div className="login-wrap">
        <div className="login-card">
          <p className="eyebrow dark">Halo, {user.nama || user.username}</p>
          <h1 className="login-h1">Ganti sandi dulu</h1>
          <p className="muted small nudge">
            Akun ini masih memakai sandi bawaan dari Super Admin. Buat sandi sendiri sebelum masuk.
          </p>
          <Field label="Sandi bawaan">
            <input type="password" value={lama} onChange={(e) => setLama(e.target.value)} autoComplete="current-password" />
          </Field>
          <Field label="Sandi baru">
            <input type="password" value={baru} onChange={(e) => setBaru(e.target.value)} autoComplete="new-password" />
          </Field>
          <Field label="Ulangi sandi baru">
            <input
              type="password"
              value={ulang}
              onChange={(e) => setUlang(e.target.value)}
              autoComplete="new-password"
              onKeyDown={(e) => e.key === "Enter" && simpan()}
            />
          </Field>
          {err ? <p className="login-err">{err}</p> : null}
          <button className="btn btn-primary full" onClick={simpan} disabled={sibuk}>
            {sibuk ? "Menyimpan…" : "Simpan dan masuk"}
          </button>
          <button className="btn btn-ghost full" onClick={onLogout}>
            Keluar
          </button>
        </div>
      </div>
    </div>
  );
}

function App() {
  const [user, setUser] = useState(null);
  const [cek, setCek] = useState(true);

  useEffect(() => {
    (async () => {
      try {
        setUser(await api("me"));
      } catch (e) {
        /* belum masuk */
      }
      setCek(false);
    })();
  }, []);

  const keluar = async () => {
    try {
      await api("logout", {});
    } catch (e) {
      /* abaikan */
    }
    setUser(null);
  };

  if (cek)
    return (
      <div className="app">
        <Style />
        <div className="loading">Menghubungi server…</div>
      </div>
    );
  if (!user) return <Login onMasuk={setUser} />;
  if (user.wajib_ganti)
    return (
      <WajibGanti
        user={user}
        onLogout={keluar}
        onSelesai={async () => {
          try {
            setUser(await api("me"));
          } catch (e) {
            setUser({ ...user, wajib_ganti: 0 });
          }
        }}
      />
    );
  return <FleetMonitor user={user} onLogout={keluar} />;
}

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