// Mock data — Bengkel Mitra Sejahtera (BMS)
// Bengkel umum modern Jakarta, 3 cabang, 180 kendaraan customer aktif,
// fokus ke reminder servis, vehicle health, dan transparansi riwayat.

const TENANT = {
  name: 'Bengkel Mitra Sejahtera',
  legal: 'PT Mitra Sejahtera Otomotif',
  short: 'BMS',
  npwp: '02.654.987.1-024.000',
  hq: 'Jl. Yos Sudarso Kav. 88, Sunter, Jakarta Utara',
  phone: '021-6589-4400',
  wa: '+62 811-1900-4400',
  email: 'cs@bengkelmitra.id',
  web: 'bengkelmitra.id',
  segment: 'Bengkel umum — servis berkala, perbaikan, sparepart, all merk (mobil, motor, komersial ringan)',
  established: 2014,
};

const TODAY = '2026-05-21';
const NOW_HHMM = '09:42';
const SHIFT = 'Pagi (08:00 — 16:00)';

const PERIOD_LIST = [
  { id: '2026-03', label: 'Maret 2026' },
  { id: '2026-04', label: 'April 2026' },
  { id: '2026-05', label: 'Mei 2026' },
];
const CURRENT_PERIOD = '2026-05';

// Cabang
const CABANG = [
  { id: 'snt', name: 'HQ Sunter',     addr: 'Jl. Yos Sudarso Kav. 88, Sunter, Jakut',    lat: -6.1438, lng: 106.8625, bay: 8, head: 'Pak Hendro Wijaya' },
  { id: 'cld', name: 'Cabang Cilandak', addr: 'Jl. RA Kartini No. 145, Cilandak, Jaksel', lat: -6.2842, lng: 106.7995, bay: 6, head: 'Pak Rizky Pratama' },
  { id: 'bks', name: 'Cabang Bekasi',  addr: 'Jl. Ahmad Yani No. 220, Bekasi Barat',     lat: -6.2419, lng: 106.9923, bay: 5, head: 'Pak Joko Susilo' },
];

// Roles
const ROLES = {
  admin: {
    id: 'admin', name: 'Bu Sari Wulandari', title: 'Admin / CS', initials: 'SW', cabang: 'snt',
    can: { view_dashboard: true, manage_booking: true, manage_wo: false, view_sparepart: true, manage_sparepart: false, view_customer: true, manage_customer: true, view_report: false, finance: false, settings: false, customer_view_own: false },
  },
  sa: {
    id: 'sa', name: 'Pak Bayu Anggara', title: 'Service Advisor', initials: 'BA', cabang: 'snt',
    can: { view_dashboard: true, manage_booking: true, manage_wo: true, view_sparepart: true, manage_sparepart: false, view_customer: true, manage_customer: false, view_report: false, finance: false, settings: false, customer_view_own: false },
  },
  mekanik: {
    id: 'mekanik', name: 'Mas Dedi Kurniawan', title: 'Mekanik Senior', initials: 'DK', cabang: 'snt',
    can: { view_dashboard: true, manage_booking: false, manage_wo: true, view_sparepart: true, manage_sparepart: false, view_customer: false, manage_customer: false, view_report: false, finance: false, settings: false, customer_view_own: false },
  },
  kasir: {
    id: 'kasir', name: 'Mbak Lina Permata', title: 'Kasir', initials: 'LP', cabang: 'snt',
    can: { view_dashboard: true, manage_booking: false, manage_wo: false, view_sparepart: true, manage_sparepart: true, view_customer: true, manage_customer: false, view_report: true, finance: true, settings: false, customer_view_own: false },
  },
  manajer: {
    id: 'manajer', name: 'Pak Hendro Wijaya', title: 'Manajer / Owner', initials: 'HW', cabang: 'snt',
    can: { view_dashboard: true, manage_booking: true, manage_wo: true, view_sparepart: true, manage_sparepart: true, view_customer: true, manage_customer: true, view_report: true, finance: true, settings: true, customer_view_own: false },
  },
  customer: {
    id: 'customer', name: 'Pak Andika Reksa', title: 'Customer — Owner Mobil', initials: 'AR', cabang: '—',
    can: { view_dashboard: false, manage_booking: true, manage_wo: false, view_sparepart: false, manage_sparepart: false, view_customer: false, manage_customer: false, view_report: false, finance: false, settings: false, customer_view_own: true },
  },
};

// Kategori kendaraan
const KATEGORI = [
  { id: 'all',        label: 'Semua' },
  { id: 'mobil',      label: 'Mobil Penumpang' },
  { id: 'motor',      label: 'Motor' },
  { id: 'komersial',  label: 'Komersial Ringan' },
  { id: 'matic',      label: 'Transmisi Matic' },
  { id: 'manual',     label: 'Transmisi Manual' },
];

// Mekanik (6 orang)
const MEKANIK = [
  { id: 'mk1', name: 'Dedi Kurniawan',  initials: 'DK', cabang: 'snt', level: 'Senior',   spec: 'Mesin & Transmisi', rating: 4.9, jobs: 412 },
  { id: 'mk2', name: 'Agus Setiawan',   initials: 'AS', cabang: 'snt', level: 'Senior',   spec: 'Kelistrikan & AC',  rating: 4.8, jobs: 378 },
  { id: 'mk3', name: 'Rudi Hartono',    initials: 'RH', cabang: 'cld', level: 'Madya',    spec: 'Kaki-kaki & Rem',   rating: 4.7, jobs: 256 },
  { id: 'mk4', name: 'Iwan Setiabudi',  initials: 'IS', cabang: 'cld', level: 'Madya',    spec: 'Body & Las',        rating: 4.6, jobs: 198 },
  { id: 'mk5', name: 'Bambang Susanto', initials: 'BS', cabang: 'bks', level: 'Senior',   spec: 'Diesel & Komersial',rating: 4.9, jobs: 344 },
  { id: 'mk6', name: 'Hendra Pratama',  initials: 'HP', cabang: 'bks', level: 'Junior',   spec: 'Motor & Skutik',    rating: 4.5, jobs: 142 },
];
const MEKANIK_BY_ID = Object.fromEntries(MEKANIK.map(m => [m.id, m]));

// Owner / customer (20 contoh; counter tenant tetap 120)
const OWNER = [
  { id: 'ow01', name: 'Andika Reksa',      phone: '0811-1212-3401', email: 'andika.r@gmail.com',     cabang: 'snt', joined: '2021-03-12', kendaraan: 2, totalSpend: 18450000, lastVisit: '2026-04-18', family: 3 },
  { id: 'ow02', name: 'Sri Hartati',       phone: '0812-2323-4502', email: 'sri.hartati@yahoo.com',  cabang: 'snt', joined: '2019-08-22', kendaraan: 3, totalSpend: 32100000, lastVisit: '2026-05-05', family: 4 },
  { id: 'ow03', name: 'PT Logistik Jaya',  phone: '021-4567-8801',  email: 'fleet@logistikjaya.id',  cabang: 'snt', joined: '2018-01-15', kendaraan: 8, totalSpend: 142000000, lastVisit: '2026-05-19', family: 0, corporate: true },
  { id: 'ow04', name: 'Budi Santoso',      phone: '0813-4545-6703', email: 'budi.s@outlook.com',     cabang: 'cld', joined: '2022-06-30', kendaraan: 1, totalSpend: 6840000, lastVisit: '2026-03-22', family: 2 },
  { id: 'ow05', name: 'Linda Permata',     phone: '0815-5656-7804', email: 'linda.p@gmail.com',      cabang: 'snt', joined: '2020-11-08', kendaraan: 2, totalSpend: 14250000, lastVisit: '2026-05-12', family: 3 },
  { id: 'ow06', name: 'Faisal Rahman',     phone: '0817-6767-8905', email: 'faisal.r@gmail.com',     cabang: 'bks', joined: '2023-02-14', kendaraan: 1, totalSpend: 4200000, lastVisit: '2026-04-30', family: 2 },
  { id: 'ow07', name: 'Maria Christine',   phone: '0818-7878-9006', email: 'maria.c@gmail.com',      cabang: 'cld', joined: '2019-05-20', kendaraan: 2, totalSpend: 21800000, lastVisit: '2026-05-08', family: 4 },
  { id: 'ow08', name: 'Yusuf Maulana',     phone: '0819-8989-0107', email: 'yusuf.m@gmail.com',      cabang: 'snt', joined: '2024-01-09', kendaraan: 1, totalSpend: 2150000, lastVisit: '2026-05-15', family: 1 },
  { id: 'ow09', name: 'CV Berkah Mandiri', phone: '021-7890-2203',  email: 'admin@berkahmandiri.id', cabang: 'bks', joined: '2017-07-04', kendaraan: 6, totalSpend: 98500000, lastVisit: '2026-05-20', family: 0, corporate: true },
  { id: 'ow10', name: 'Tantowi Hadi',      phone: '0821-2323-4308', email: 'tantowi.h@gmail.com',    cabang: 'snt', joined: '2020-09-18', kendaraan: 2, totalSpend: 19200000, lastVisit: '2026-04-25', family: 3 },
  { id: 'ow11', name: 'Diah Ayu Sari',     phone: '0822-3434-5409', email: 'diah.ayu@gmail.com',     cabang: 'cld', joined: '2022-12-01', kendaraan: 1, totalSpend: 5100000, lastVisit: '2026-05-10', family: 2 },
  { id: 'ow12', name: 'Rangga Pradipta',   phone: '0823-4545-6510', email: 'rangga.p@gmail.com',     cabang: 'snt', joined: '2021-08-25', kendaraan: 1, totalSpend: 7800000, lastVisit: '2026-05-17', family: 2 },
  { id: 'ow13', name: 'Bu Yanti',          phone: '0851-5656-7611', email: '-',                       cabang: 'bks', joined: '2023-11-12', kendaraan: 1, totalSpend: 1850000, lastVisit: '2026-04-12', family: 1 },
  { id: 'ow14', name: 'Pak Sutrisno',      phone: '0852-6767-8712', email: 'sutrisno@gmail.com',     cabang: 'snt', joined: '2018-04-03', kendaraan: 4, totalSpend: 45600000, lastVisit: '2026-05-14', family: 5 },
  { id: 'ow15', name: 'Kevin Wijaya',      phone: '0853-7878-9813', email: 'kevin.w@gmail.com',      cabang: 'cld', joined: '2024-08-19', kendaraan: 1, totalSpend: 980000, lastVisit: '2026-05-19', family: 1 },
  { id: 'ow16', name: 'Sandra Devi',       phone: '0856-8989-0914', email: 'sandra.d@gmail.com',     cabang: 'snt', joined: '2020-02-28', kendaraan: 2, totalSpend: 16400000, lastVisit: '2026-04-29', family: 3 },
  { id: 'ow17', name: 'Galih Pramudita',   phone: '0857-9090-1215', email: 'galih.p@gmail.com',      cabang: 'bks', joined: '2022-05-16', kendaraan: 1, totalSpend: 6200000, lastVisit: '2026-05-11', family: 2 },
  { id: 'ow18', name: 'Ratih Kumala',      phone: '0858-0101-2316', email: 'ratih.k@gmail.com',      cabang: 'cld', joined: '2021-12-22', kendaraan: 1, totalSpend: 8950000, lastVisit: '2026-05-07', family: 2 },
  { id: 'ow19', name: 'Pak Wagimin',       phone: '0859-1212-3417', email: '-',                       cabang: 'bks', joined: '2019-10-05', kendaraan: 2, totalSpend: 22300000, lastVisit: '2026-05-18', family: 4 },
  { id: 'ow20', name: 'Vincent Halim',     phone: '0878-2323-4518', email: 'vincent.h@gmail.com',    cabang: 'snt', joined: '2025-01-30', kendaraan: 1, totalSpend: 1450000, lastVisit: '2026-05-16', family: 1 },
];
const OWNER_BY_ID = Object.fromEntries(OWNER.map(o => [o.id, o]));

// Komponen health master (per kendaraan akan punya value berbeda)
const HEALTH_COMPONENTS = [
  { id: 'oli_mesin',  label: 'Oli Mesin',         icon: 'oil',        intervalKm: 5000,   intervalMo: 6  },
  { id: 'oli_trans',  label: 'Oli Transmisi',     icon: 'droplet',    intervalKm: 40000,  intervalMo: 24 },
  { id: 'filter_oli', label: 'Filter Oli',        icon: 'filter',     intervalKm: 10000,  intervalMo: 12 },
  { id: 'filter_udr', label: 'Filter Udara',      icon: 'filter',     intervalKm: 20000,  intervalMo: 12 },
  { id: 'aki',        label: 'Aki',               icon: 'battery',    intervalKm: 50000,  intervalMo: 24 },
  { id: 'rem_dpn',    label: 'Kampas Rem Depan',  icon: 'brakeDisc',  intervalKm: 30000,  intervalMo: 18 },
  { id: 'rem_blk',    label: 'Kampas Rem Blkg',   icon: 'brakeDisc',  intervalKm: 40000,  intervalMo: 24 },
  { id: 'ban',        label: 'Ban',               icon: 'tire',       intervalKm: 50000,  intervalMo: 36 },
  { id: 'busi',       label: 'Busi',              icon: 'sparkplug',  intervalKm: 20000,  intervalMo: 12 },
  { id: 'ac',         label: 'AC / Freon',        icon: 'ac',         intervalKm: 30000,  intervalMo: 18 },
  { id: 'kopling',    label: 'Kopling',           icon: 'gear',       intervalKm: 80000,  intervalMo: 36 },
  { id: 'radiator',   label: 'Air Radiator',      icon: 'thermo',     intervalKm: 20000,  intervalMo: 12 },
];
const HEALTH_COMP_BY_ID = Object.fromEntries(HEALTH_COMPONENTS.map(c => [c.id, c]));

// Helper: bikin map status komponen per kendaraan (km terpakai vs interval)
function _comp(km, mo, opts = {}) {
  // km/mo = pemakaian sejak servis terakhir
  return { km, mo, note: opts.note || null };
}

// Kendaraan customer (25 contoh; counter tenant 180)
const KENDARAAN = [
  // mobil penumpang
  { id: 'vh001', plat: 'B 1234 ABC',  merk: 'Toyota',   model: 'Avanza',      tipe: 'mobil',     trans: 'manual', tahun: 2019, warna: 'Putih',  km: 86400, ownerId: 'ow01', cabang: 'snt', stnkExp: '2026-08-15', pajakExp: '2026-08-15', asuransiExp: '2027-02-10', kirExp: null, lastService: '2025-12-10', lastServiceKm: 82100, photo: null, components: { oli_mesin: _comp(4300, 5),  filter_oli: _comp(4300, 5),  filter_udr: _comp(8200, 11), aki: _comp(28000, 14), rem_dpn: _comp(18500, 9), rem_blk: _comp(22000, 12), ban: _comp(38000, 28), busi: _comp(15000, 11), ac: _comp(12000, 9),  kopling: _comp(58000, 30), radiator: _comp(8200, 11), oli_trans: _comp(28000, 14) } },
  { id: 'vh002', plat: 'B 5678 DEF',  merk: 'Honda',    model: 'Brio Satya',  tipe: 'mobil',     trans: 'matic',  tahun: 2022, warna: 'Merah',  km: 32200, ownerId: 'ow01', cabang: 'snt', stnkExp: '2027-03-20', pajakExp: '2026-09-20', asuransiExp: '2026-09-20', kirExp: null, lastService: '2026-03-15', lastServiceKm: 28500, photo: null, components: { oli_mesin: _comp(3700, 2),  filter_oli: _comp(3700, 2),  filter_udr: _comp(3700, 2),  aki: _comp(32200, 36), rem_dpn: _comp(28500, 26), rem_blk: _comp(32200, 36), ban: _comp(32200, 36), busi: _comp(3700, 2),  ac: _comp(3700, 2),   kopling: _comp(3700, 2),  radiator: _comp(3700, 2),  oli_trans: _comp(32200, 36) } },
  { id: 'vh003', plat: 'B 9012 GHI',  merk: 'Daihatsu', model: 'Xenia',       tipe: 'mobil',     trans: 'manual', tahun: 2018, warna: 'Silver', km: 124800, ownerId: 'ow02', cabang: 'snt', stnkExp: '2026-05-30', pajakExp: '2026-05-30', asuransiExp: '2026-11-15', kirExp: null, lastService: '2025-11-22', lastServiceKm: 119500, photo: null, components: { oli_mesin: _comp(5300, 6),  filter_oli: _comp(5300, 6),  filter_udr: _comp(10500, 12), aki: _comp(45000, 22), rem_dpn: _comp(28500, 18), rem_blk: _comp(38000, 22), ban: _comp(48000, 32), busi: _comp(18000, 12), ac: _comp(28000, 17), kopling: _comp(75000, 34), radiator: _comp(18000, 12), oli_trans: _comp(38000, 22) } },
  { id: 'vh004', plat: 'B 3456 JKL',  merk: 'Toyota',   model: 'Innova Reborn',tipe: 'mobil',    trans: 'matic',  tahun: 2021, warna: 'Hitam',  km: 68500, ownerId: 'ow02', cabang: 'snt', stnkExp: '2026-07-12', pajakExp: '2026-07-12', asuransiExp: '2026-12-30', kirExp: null, lastService: '2026-02-08', lastServiceKm: 64200, photo: null, components: { oli_mesin: _comp(4300, 3),  filter_oli: _comp(4300, 3),  filter_udr: _comp(8500, 7),  aki: _comp(38000, 18), rem_dpn: _comp(22000, 14), rem_blk: _comp(28000, 18), ban: _comp(42000, 24), busi: _comp(15000, 8),  ac: _comp(18000, 12), kopling: _comp(48000, 24), radiator: _comp(15000, 8),  oli_trans: _comp(38000, 18) } },
  { id: 'vh005', plat: 'B 7890 MNO',  merk: 'Mitsubishi',model: 'Xpander',    tipe: 'mobil',     trans: 'matic',  tahun: 2020, warna: 'Putih',  km: 92300, ownerId: 'ow02', cabang: 'snt', stnkExp: '2026-06-05', pajakExp: '2026-06-05', asuransiExp: '2026-10-08', kirExp: null, lastService: '2026-04-02', lastServiceKm: 89800, photo: null, components: { oli_mesin: _comp(2500, 1),  filter_oli: _comp(2500, 1),  filter_udr: _comp(8500, 5),  aki: _comp(32000, 14), rem_dpn: _comp(18000, 10), rem_blk: _comp(22000, 14), ban: _comp(38000, 20), busi: _comp(12000, 5),  ac: _comp(15000, 8),  kopling: _comp(45000, 20), radiator: _comp(12000, 5),  oli_trans: _comp(32000, 14) } },

  // PT Logistik Jaya — fleet 8 (3 contoh)
  { id: 'vh006', plat: 'B 8001 LJ',   merk: 'Mitsubishi',model: 'L300 Pickup',tipe: 'komersial', trans: 'manual', tahun: 2020, warna: 'Putih',  km: 245000, ownerId: 'ow03', cabang: 'snt', stnkExp: '2026-09-15', pajakExp: '2026-09-15', asuransiExp: '2026-12-20', kirExp: '2026-06-30', lastService: '2026-04-28', lastServiceKm: 242000, photo: null, components: { oli_mesin: _comp(3000, 1),  filter_oli: _comp(3000, 1),  filter_udr: _comp(8500, 5),  aki: _comp(48000, 18), rem_dpn: _comp(25000, 12), rem_blk: _comp(38000, 18), ban: _comp(45000, 22), busi: _comp(18000, 8),  ac: _comp(18000, 10), kopling: _comp(72000, 24), radiator: _comp(18000, 8),  oli_trans: _comp(38000, 18) } },
  { id: 'vh007', plat: 'B 8002 LJ',   merk: 'Daihatsu', model: 'Grandmax Box',tipe: 'komersial', trans: 'manual', tahun: 2021, warna: 'Putih',  km: 198000, ownerId: 'ow03', cabang: 'snt', stnkExp: '2026-11-20', pajakExp: '2026-11-20', asuransiExp: '2026-09-10', kirExp: '2026-05-25', lastService: '2026-05-10', lastServiceKm: 196500, photo: null, components: { oli_mesin: _comp(1500, 0),  filter_oli: _comp(1500, 0),  filter_udr: _comp(6500, 3),  aki: _comp(38000, 14), rem_dpn: _comp(18000, 8),  rem_blk: _comp(28000, 14), ban: _comp(35000, 18), busi: _comp(8000, 3),   ac: _comp(15000, 8),  kopling: _comp(52000, 20), radiator: _comp(15000, 8),  oli_trans: _comp(28000, 14) } },
  { id: 'vh008', plat: 'B 8003 LJ',   merk: 'Suzuki',   model: 'Carry Pickup',tipe: 'komersial', trans: 'manual', tahun: 2019, warna: 'Putih',  km: 312000, ownerId: 'ow03', cabang: 'snt', stnkExp: '2026-05-28', pajakExp: '2026-05-28', asuransiExp: '2026-08-15', kirExp: '2026-04-12', lastService: '2026-03-20', lastServiceKm: 308000, photo: null, components: { oli_mesin: _comp(4000, 2),  filter_oli: _comp(4000, 2),  filter_udr: _comp(8500, 6),  aki: _comp(52000, 22), rem_dpn: _comp(28000, 14), rem_blk: _comp(42000, 22), ban: _comp(52000, 28), busi: _comp(18000, 9),  ac: _comp(22000, 12), kopling: _comp(82000, 32), radiator: _comp(18000, 9),  oli_trans: _comp(42000, 22) } },

  // mobil lain
  { id: 'vh009', plat: 'B 2468 PQR',  merk: 'Honda',    model: 'Mobilio',     tipe: 'mobil',     trans: 'matic',  tahun: 2019, warna: 'Abu-abu',km: 98700, ownerId: 'ow04', cabang: 'cld', stnkExp: '2026-10-08', pajakExp: '2026-10-08', asuransiExp: '2027-01-22', kirExp: null, lastService: '2026-01-18', lastServiceKm: 93200, photo: null, components: { oli_mesin: _comp(5500, 4),  filter_oli: _comp(5500, 4),  filter_udr: _comp(11000, 8),  aki: _comp(42000, 20), rem_dpn: _comp(25000, 14), rem_blk: _comp(32000, 20), ban: _comp(42000, 24), busi: _comp(18000, 8),  ac: _comp(22000, 12), kopling: _comp(58000, 28), radiator: _comp(18000, 8),  oli_trans: _comp(42000, 20) } },
  { id: 'vh010', plat: 'B 1357 STU',  merk: 'Toyota',   model: 'Calya',       tipe: 'mobil',     trans: 'manual', tahun: 2020, warna: 'Putih',  km: 75400, ownerId: 'ow05', cabang: 'snt', stnkExp: '2026-12-15', pajakExp: '2026-12-15', asuransiExp: '2026-11-30', kirExp: null, lastService: '2026-04-15', lastServiceKm: 72800, photo: null, components: { oli_mesin: _comp(2600, 1),  filter_oli: _comp(2600, 1),  filter_udr: _comp(5500, 4),  aki: _comp(32000, 14), rem_dpn: _comp(18000, 10), rem_blk: _comp(22000, 14), ban: _comp(35000, 18), busi: _comp(12000, 5),  ac: _comp(15000, 8),  kopling: _comp(42000, 20), radiator: _comp(12000, 5),  oli_trans: _comp(32000, 14) } },
  { id: 'vh011', plat: 'B 8642 VWX',  merk: 'Suzuki',   model: 'Ertiga',      tipe: 'mobil',     trans: 'matic',  tahun: 2021, warna: 'Coklat', km: 54200, ownerId: 'ow05', cabang: 'snt', stnkExp: '2027-01-10', pajakExp: '2026-07-10', asuransiExp: '2026-12-15', kirExp: null, lastService: '2026-02-22', lastServiceKm: 49800, photo: null, components: { oli_mesin: _comp(4400, 3),  filter_oli: _comp(4400, 3),  filter_udr: _comp(9000, 6),  aki: _comp(28000, 12), rem_dpn: _comp(16000, 8),  rem_blk: _comp(22000, 12), ban: _comp(32000, 16), busi: _comp(12000, 6),  ac: _comp(16000, 8),  kopling: _comp(38000, 18), radiator: _comp(12000, 6),  oli_trans: _comp(28000, 12) } },
  { id: 'vh012', plat: 'B 7531 YZA',  merk: 'Honda',    model: 'HR-V',        tipe: 'mobil',     trans: 'matic',  tahun: 2022, warna: 'Hitam',  km: 38500, ownerId: 'ow07', cabang: 'cld', stnkExp: '2027-04-25', pajakExp: '2026-10-25', asuransiExp: '2027-03-08', kirExp: null, lastService: '2026-03-30', lastServiceKm: 35200, photo: null, components: { oli_mesin: _comp(3300, 2),  filter_oli: _comp(3300, 2),  filter_udr: _comp(3300, 2),  aki: _comp(28000, 12), rem_dpn: _comp(18000, 8),  rem_blk: _comp(22000, 12), ban: _comp(32000, 16), busi: _comp(12000, 6),  ac: _comp(15000, 8),  kopling: _comp(28000, 12), radiator: _comp(12000, 6),  oli_trans: _comp(28000, 12) } },
  { id: 'vh013', plat: 'B 9753 BCD',  merk: 'Toyota',   model: 'Rush',        tipe: 'mobil',     trans: 'matic',  tahun: 2020, warna: 'Merah',  km: 82400, ownerId: 'ow07', cabang: 'cld', stnkExp: '2026-08-22', pajakExp: '2026-08-22', asuransiExp: '2026-12-05', kirExp: null, lastService: '2026-03-12', lastServiceKm: 78900, photo: null, components: { oli_mesin: _comp(3500, 2),  filter_oli: _comp(3500, 2),  filter_udr: _comp(7000, 5),  aki: _comp(38000, 18), rem_dpn: _comp(22000, 12), rem_blk: _comp(28000, 18), ban: _comp(38000, 20), busi: _comp(15000, 8),  ac: _comp(18000, 10), kopling: _comp(48000, 24), radiator: _comp(15000, 8),  oli_trans: _comp(38000, 18) } },

  // motor
  { id: 'vh014', plat: 'B 4567 EFG',  merk: 'Honda',    model: 'Vario 160',   tipe: 'motor',     trans: 'matic',  tahun: 2023, warna: 'Hitam',  km: 18500, ownerId: 'ow08', cabang: 'snt', stnkExp: '2028-01-15', pajakExp: '2027-01-15', asuransiExp: null,         kirExp: null, lastService: '2026-04-10', lastServiceKm: 16500, photo: null, components: { oli_mesin: _comp(2000, 1),  filter_oli: _comp(2000, 1),  filter_udr: _comp(8500, 6),  aki: _comp(18500, 14), rem_dpn: _comp(12000, 10), rem_blk: _comp(16500, 14), ban: _comp(18500, 14), busi: _comp(8500, 6),   ac: null,            kopling: null,           radiator: _comp(8500, 6),  oli_trans: _comp(18500, 14) } },
  { id: 'vh015', plat: 'B 2345 HIJ',  merk: 'Yamaha',   model: 'NMAX',        tipe: 'motor',     trans: 'matic',  tahun: 2022, warna: 'Putih',  km: 28200, ownerId: 'ow10', cabang: 'snt', stnkExp: '2027-06-20', pajakExp: '2026-06-20', asuransiExp: null,         kirExp: null, lastService: '2026-03-05', lastServiceKm: 25800, photo: null, components: { oli_mesin: _comp(2400, 2),  filter_oli: _comp(2400, 2),  filter_udr: _comp(8500, 7),  aki: _comp(28200, 22), rem_dpn: _comp(18000, 14), rem_blk: _comp(22000, 18), ban: _comp(28200, 22), busi: _comp(12000, 8),  ac: null,            kopling: null,           radiator: _comp(12000, 8), oli_trans: _comp(28200, 22) } },
  { id: 'vh016', plat: 'B 6789 KLM',  merk: 'Honda',    model: 'PCX 160',     tipe: 'motor',     trans: 'matic',  tahun: 2024, warna: 'Putih',  km: 8500,  ownerId: 'ow15', cabang: 'cld', stnkExp: '2029-03-08', pajakExp: '2027-03-08', asuransiExp: '2027-03-08', kirExp: null, lastService: '2026-04-22', lastServiceKm: 7200,  photo: null, components: { oli_mesin: _comp(1300, 1),  filter_oli: _comp(1300, 1),  filter_udr: _comp(1300, 1),  aki: _comp(8500, 6),   rem_dpn: _comp(8500, 6),   rem_blk: _comp(8500, 6),   ban: _comp(8500, 6),   busi: _comp(8500, 6),   ac: null,            kopling: null,           radiator: _comp(1300, 1),  oli_trans: _comp(8500, 6) } },

  // CV Berkah Mandiri fleet
  { id: 'vh017', plat: 'B 9501 BM',   merk: 'Isuzu',    model: 'Traga',       tipe: 'komersial', trans: 'manual', tahun: 2021, warna: 'Putih',  km: 168000, ownerId: 'ow09', cabang: 'bks', stnkExp: '2026-11-30', pajakExp: '2026-11-30', asuransiExp: '2026-10-20', kirExp: '2026-07-15', lastService: '2026-04-15', lastServiceKm: 164000, photo: null, components: { oli_mesin: _comp(4000, 1),  filter_oli: _comp(4000, 1),  filter_udr: _comp(8500, 5),  aki: _comp(38000, 16), rem_dpn: _comp(22000, 10), rem_blk: _comp(32000, 16), ban: _comp(42000, 20), busi: _comp(18000, 8),  ac: _comp(18000, 10), kopling: _comp(58000, 22), radiator: _comp(18000, 8),  oli_trans: _comp(32000, 16) } },
  { id: 'vh018', plat: 'B 9502 BM',   merk: 'Mitsubishi',model: 'Colt Diesel',tipe: 'komersial', trans: 'manual', tahun: 2019, warna: 'Kuning', km: 285000, ownerId: 'ow09', cabang: 'bks', stnkExp: '2026-06-12', pajakExp: '2026-06-12', asuransiExp: '2026-11-05', kirExp: '2026-05-30', lastService: '2026-03-08', lastServiceKm: 280500, photo: null, components: { oli_mesin: _comp(4500, 2),  filter_oli: _comp(4500, 2),  filter_udr: _comp(9000, 6),  aki: _comp(48000, 22), rem_dpn: _comp(28000, 14), rem_blk: _comp(42000, 22), ban: _comp(48000, 26), busi: _comp(22000, 10), ac: _comp(22000, 12), kopling: _comp(78000, 30), radiator: _comp(22000, 10), oli_trans: _comp(42000, 22) } },

  // sisanya
  { id: 'vh019', plat: 'B 3702 NOP',  merk: 'Daihatsu', model: 'Terios',      tipe: 'mobil',     trans: 'manual', tahun: 2018, warna: 'Hitam',  km: 132500, ownerId: 'ow11', cabang: 'cld', stnkExp: '2026-09-18', pajakExp: '2026-09-18', asuransiExp: '2026-12-10', kirExp: null, lastService: '2026-02-28', lastServiceKm: 128200, photo: null, components: { oli_mesin: _comp(4300, 3),  filter_oli: _comp(4300, 3),  filter_udr: _comp(8500, 6),  aki: _comp(42000, 20), rem_dpn: _comp(28000, 16), rem_blk: _comp(35000, 20), ban: _comp(42000, 24), busi: _comp(18000, 9),  ac: _comp(22000, 12), kopling: _comp(62000, 28), radiator: _comp(18000, 9),  oli_trans: _comp(35000, 20) } },
  { id: 'vh020', plat: 'B 1928 QRS',  merk: 'Hyundai',  model: 'Creta',       tipe: 'mobil',     trans: 'matic',  tahun: 2023, warna: 'Putih',  km: 22800, ownerId: 'ow12', cabang: 'snt', stnkExp: '2028-04-20', pajakExp: '2027-04-20', asuransiExp: '2027-04-20', kirExp: null, lastService: '2026-04-08', lastServiceKm: 19500, photo: null, components: { oli_mesin: _comp(3300, 2),  filter_oli: _comp(3300, 2),  filter_udr: _comp(3300, 2),  aki: _comp(22800, 10), rem_dpn: _comp(15000, 7),  rem_blk: _comp(22800, 10), ban: _comp(22800, 10), busi: _comp(12000, 6),  ac: _comp(15000, 8),  kopling: _comp(22800, 10), radiator: _comp(12000, 6),  oli_trans: _comp(22800, 10) } },
  { id: 'vh021', plat: 'B 4815 TUV',  merk: 'Honda',    model: 'BeAT',        tipe: 'motor',     trans: 'matic',  tahun: 2020, warna: 'Merah',  km: 42500, ownerId: 'ow13', cabang: 'bks', stnkExp: '2025-10-15', pajakExp: '2026-04-15', asuransiExp: null,         kirExp: null, lastService: '2026-04-12', lastServiceKm: 41000, photo: null, components: { oli_mesin: _comp(1500, 1),  filter_oli: _comp(1500, 1),  filter_udr: _comp(8500, 8),  aki: _comp(42500, 30), rem_dpn: _comp(28000, 22), rem_blk: _comp(35000, 28), ban: _comp(42500, 30), busi: _comp(18000, 12), ac: null,            kopling: null,           radiator: _comp(18000, 12),oli_trans: _comp(42500, 30) } },
  { id: 'vh022', plat: 'B 5926 WXY',  merk: 'Toyota',   model: 'Fortuner',    tipe: 'mobil',     trans: 'matic',  tahun: 2020, warna: 'Hitam',  km: 102400, ownerId: 'ow14', cabang: 'snt', stnkExp: '2026-07-25', pajakExp: '2026-07-25', asuransiExp: '2027-01-18', kirExp: null, lastService: '2026-04-05', lastServiceKm: 99500, photo: null, components: { oli_mesin: _comp(2900, 1),  filter_oli: _comp(2900, 1),  filter_udr: _comp(8500, 5),  aki: _comp(38000, 18), rem_dpn: _comp(22000, 12), rem_blk: _comp(28000, 18), ban: _comp(42000, 22), busi: _comp(15000, 7),  ac: _comp(18000, 10), kopling: _comp(58000, 24), radiator: _comp(15000, 7),  oli_trans: _comp(38000, 18) } },
  { id: 'vh023', plat: 'B 6037 ZAB',  merk: 'Honda',    model: 'Jazz',        tipe: 'mobil',     trans: 'matic',  tahun: 2017, warna: 'Putih',  km: 145800, ownerId: 'ow16', cabang: 'snt', stnkExp: '2026-08-30', pajakExp: '2026-08-30', asuransiExp: '2026-09-22', kirExp: null, lastService: '2025-10-15', lastServiceKm: 140500, photo: null, components: { oli_mesin: _comp(5300, 7),  filter_oli: _comp(5300, 7),  filter_udr: _comp(10500, 14), aki: _comp(48000, 24), rem_dpn: _comp(32000, 20), rem_blk: _comp(38000, 24), ban: _comp(48000, 32), busi: _comp(20000, 14), ac: _comp(28000, 18), kopling: _comp(72000, 34), radiator: _comp(20000, 14), oli_trans: _comp(38000, 24) } },
  { id: 'vh024', plat: 'B 7148 CDE',  merk: 'Suzuki',   model: 'XL7',         tipe: 'mobil',     trans: 'matic',  tahun: 2022, warna: 'Silver', km: 45200, ownerId: 'ow18', cabang: 'cld', stnkExp: '2027-02-18', pajakExp: '2026-08-18', asuransiExp: '2026-12-28', kirExp: null, lastService: '2026-02-15', lastServiceKm: 41500, photo: null, components: { oli_mesin: _comp(3700, 3),  filter_oli: _comp(3700, 3),  filter_udr: _comp(7500, 6),  aki: _comp(28000, 12), rem_dpn: _comp(18000, 10), rem_blk: _comp(22000, 12), ban: _comp(32000, 16), busi: _comp(15000, 8),  ac: _comp(18000, 10), kopling: _comp(42000, 20), radiator: _comp(15000, 8),  oli_trans: _comp(28000, 12) } },
  { id: 'vh025', plat: 'B 8259 FGH',  merk: 'Mitsubishi',model: 'Pajero Sport',tipe: 'mobil',    trans: 'matic',  tahun: 2019, warna: 'Putih',  km: 118400, ownerId: 'ow19', cabang: 'bks', stnkExp: '2026-06-08', pajakExp: '2026-06-08', asuransiExp: '2026-10-15', kirExp: null, lastService: '2026-04-20', lastServiceKm: 115200, photo: null, components: { oli_mesin: _comp(3200, 1),  filter_oli: _comp(3200, 1),  filter_udr: _comp(8500, 5),  aki: _comp(42000, 20), rem_dpn: _comp(25000, 14), rem_blk: _comp(32000, 20), ban: _comp(42000, 22), busi: _comp(18000, 9),  ac: _comp(22000, 12), kopling: _comp(62000, 28), radiator: _comp(18000, 9),  oli_trans: _comp(42000, 20) } },
];
const KENDARAAN_BY_ID = Object.fromEntries(KENDARAAN.map(k => [k.id, k]));

// Item servis master
const ITEM_SERVIS = [
  { id: 'is01', label: 'Ganti Oli Mesin',         hargaJasa: 50000,  durMin: 30 },
  { id: 'is02', label: 'Ganti Filter Oli',        hargaJasa: 25000,  durMin: 15 },
  { id: 'is03', label: 'Ganti Filter Udara',      hargaJasa: 30000,  durMin: 20 },
  { id: 'is04', label: 'Tune Up Komplit',         hargaJasa: 350000, durMin: 120 },
  { id: 'is05', label: 'Ganti Kampas Rem Depan',  hargaJasa: 150000, durMin: 60 },
  { id: 'is06', label: 'Ganti Kampas Rem Belakang',hargaJasa: 175000,durMin: 75 },
  { id: 'is07', label: 'Spooring & Balancing',    hargaJasa: 250000, durMin: 90 },
  { id: 'is08', label: 'Ganti Ban',               hargaJasa: 75000,  durMin: 45 },
  { id: 'is09', label: 'Ganti Aki',               hargaJasa: 50000,  durMin: 20 },
  { id: 'is10', label: 'Service AC + Freon',      hargaJasa: 350000, durMin: 150 },
  { id: 'is11', label: 'Ganti Busi',              hargaJasa: 60000,  durMin: 30 },
  { id: 'is12', label: 'Ganti Oli Transmisi',     hargaJasa: 80000,  durMin: 45 },
  { id: 'is13', label: 'Tambah/Ganti Air Radiator',hargaJasa: 40000, durMin: 30 },
  { id: 'is14', label: 'Servis Kopling',          hargaJasa: 850000, durMin: 240 },
  { id: 'is15', label: 'Cek & Setting Lampu',     hargaJasa: 35000,  durMin: 30 },
  { id: 'is16', label: 'Cuci & Detailing',        hargaJasa: 120000, durMin: 90 },
  { id: 'is17', label: 'Inspeksi Pra-Mudik',      hargaJasa: 200000, durMin: 90 },
  { id: 'is18', label: 'KIR Persiapan Komersial', hargaJasa: 175000, durMin: 60 },
];
const ITEM_SERVIS_BY_ID = Object.fromEntries(ITEM_SERVIS.map(i => [i.id, i]));

// Sparepart inventory (40 SKU, contoh 18 SKU dgn variasi merk)
const SPAREPART = [
  { id: 'sp01', sku: 'OIL-SHL-10W40-1L',  nama: 'Oli Mesin Shell Helix HX7 10W-40 1L',  kategori: 'Oli', merk: 'Shell',     hargaBeli: 65000,  hargaJual: 92000,  stok: 48, min: 20, terjual30d: 86 },
  { id: 'sp02', sku: 'OIL-CST-10W40-4L',  nama: 'Oli Mesin Castrol Magnatec 10W-40 4L',  kategori: 'Oli', merk: 'Castrol',   hargaBeli: 285000, hargaJual: 380000, stok: 22, min: 10, terjual30d: 34 },
  { id: 'sp03', sku: 'OIL-PTM-10W40-4L',  nama: 'Oli Mesin Pertamina Fastron 10W-40 4L', kategori: 'Oli', merk: 'Pertamina', hargaBeli: 175000, hargaJual: 235000, stok: 35, min: 15, terjual30d: 52 },
  { id: 'sp04', sku: 'FLT-OIL-AVZ',       nama: 'Filter Oli Avanza/Xenia',               kategori: 'Filter', merk: 'Aspira',  hargaBeli: 28000,  hargaJual: 45000,  stok: 56, min: 25, terjual30d: 68 },
  { id: 'sp05', sku: 'FLT-OIL-INV',       nama: 'Filter Oli Innova/Fortuner',            kategori: 'Filter', merk: 'Aspira',  hargaBeli: 42000,  hargaJual: 68000,  stok: 32, min: 15, terjual30d: 28 },
  { id: 'sp06', sku: 'BRK-PAD-DPN-AVZ',   nama: 'Kampas Rem Depan Avanza/Xenia',         kategori: 'Rem', merk: 'Bendix',    hargaBeli: 185000, hargaJual: 275000, stok: 14, min: 10, terjual30d: 18 },
  { id: 'sp07', sku: 'BRK-PAD-DPN-INV',   nama: 'Kampas Rem Depan Innova',               kategori: 'Rem', merk: 'Bendix',    hargaBeli: 245000, hargaJual: 365000, stok: 8,  min: 8,  terjual30d: 12 },
  { id: 'sp08', sku: 'AKI-NS40-N50Z',     nama: 'Aki GS Hybrid NS40 / NS50Z',            kategori: 'Aki', merk: 'GS Astra',  hargaBeli: 685000, hargaJual: 895000, stok: 12, min: 8,  terjual30d: 14 },
  { id: 'sp09', sku: 'AKI-N70-INC',       nama: 'Aki Incoe N70 (Diesel)',                kategori: 'Aki', merk: 'Incoe',     hargaBeli: 1250000,hargaJual: 1650000,stok: 4,  min: 4,  terjual30d: 6  },
  { id: 'sp10', sku: 'BAN-185-65-15-DLP', nama: 'Ban Dunlop SP Sport 185/65 R15',        kategori: 'Ban', merk: 'Dunlop',    hargaBeli: 685000, hargaJual: 925000, stok: 16, min: 8,  terjual30d: 22 },
  { id: 'sp11', sku: 'BAN-205-65-16-BST', nama: 'Ban Bridgestone Turanza 205/65 R16',    kategori: 'Ban', merk: 'Bridgestone',hargaBeli: 985000,hargaJual: 1325000,stok: 12, min: 8,  terjual30d: 16 },
  { id: 'sp12', sku: 'BUSI-NGK-CR7HSA',   nama: 'Busi NGK CR7HSA (Motor)',               kategori: 'Busi', merk: 'NGK',      hargaBeli: 22000,  hargaJual: 38000,  stok: 72, min: 30, terjual30d: 95 },
  { id: 'sp13', sku: 'BUSI-NGK-LFR6A',    nama: 'Busi NGK Iridium LFR6A-11 (Mobil)',     kategori: 'Busi', merk: 'NGK',      hargaBeli: 85000,  hargaJual: 135000, stok: 36, min: 20, terjual30d: 42 },
  { id: 'sp14', sku: 'FRN-R134A-1KG',     nama: 'Freon AC R134a 1kg',                    kategori: 'AC',  merk: 'Klea',      hargaBeli: 145000, hargaJual: 225000, stok: 18, min: 10, terjual30d: 24 },
  { id: 'sp15', sku: 'COOL-PRESTONE-1L',  nama: 'Coolant Prestone Hijau 1L',             kategori: 'Coolant', merk: 'Prestone',hargaBeli: 48000, hargaJual: 75000, stok: 28, min: 15, terjual30d: 32 },
  { id: 'sp16', sku: 'BLT-V-AVZ',         nama: 'V-Belt Avanza/Xenia (Power Steering)',  kategori: 'Belt', merk: 'Mitsuboshi',hargaBeli: 95000, hargaJual: 145000, stok: 14, min: 10, terjual30d: 12 },
  { id: 'sp17', sku: 'LMP-H4-PHL',        nama: 'Lampu Philips H4 60/55W',               kategori: 'Lampu', merk: 'Philips',  hargaBeli: 65000,  hargaJual: 115000, stok: 32, min: 15, terjual30d: 28 },
  { id: 'sp18', sku: 'OIL-MTR-AHM-1L',    nama: 'Oli Motor AHM SPX2 1L',                 kategori: 'Oli', merk: 'AHM',       hargaBeli: 48000,  hargaJual: 72000,  stok: 86, min: 40, terjual30d: 124 },
];
const SPAREPART_BY_ID = Object.fromEntries(SPAREPART.map(s => [s.id, s]));

// Riwayat servis (40 contoh; total tenant 250)
const SERVIS_HISTORY = [
  { id: 'sh001', tgl: '2026-05-20', vhId: 'vh017', km: 168000, items: ['is01','is02','is03'], mekanikId: 'mk5', cabang: 'bks', total: 685000, foto: 2, catatan: 'Servis berkala 168rb km, oli mesin diesel.' },
  { id: 'sh002', tgl: '2026-05-19', vhId: 'vh007', km: 198000, items: ['is01','is02','is18'], mekanikId: 'mk1', cabang: 'snt', total: 825000, foto: 3, catatan: 'Persiapan KIR + oli + filter.' },
  { id: 'sh003', tgl: '2026-05-18', vhId: 'vh025', km: 118400, items: ['is01','is02','is05'], mekanikId: 'mk5', cabang: 'bks', total: 1280000,foto: 4, catatan: 'Kampas rem depan habis, ganti.' },
  { id: 'sh004', tgl: '2026-05-17', vhId: 'vh012', km: 38500,  items: ['is04','is15'],         mekanikId: 'mk2', cabang: 'cld', total: 485000, foto: 2, catatan: 'Tune up + setting lampu setelah kena hujan.' },
  { id: 'sh005', tgl: '2026-05-16', vhId: 'vh020', km: 22800,  items: ['is16'],                 mekanikId: 'mk2', cabang: 'snt', total: 120000, foto: 1, catatan: 'Cuci detailing.' },
  { id: 'sh006', tgl: '2026-05-15', vhId: 'vh008', km: 312000, items: ['is01','is02','is07'], mekanikId: 'mk5', cabang: 'bks', total: 945000, foto: 2, catatan: 'Spooring after servis berkala. Geometri perlu diawasi.' },
  { id: 'sh007', tgl: '2026-05-14', vhId: 'vh022', km: 102400, items: ['is10','is14'],         mekanikId: 'mk2', cabang: 'snt', total: 2150000,foto: 5, catatan: 'AC kurang dingin + kopling ganti komplit.' },
  { id: 'sh008', tgl: '2026-05-13', vhId: 'vh004', km: 68500,  items: ['is01','is02','is11'], mekanikId: 'mk1', cabang: 'snt', total: 685000, foto: 2, catatan: 'Servis berkala 70rb km + ganti busi.' },
  { id: 'sh009', tgl: '2026-05-12', vhId: 'vh010', km: 75400,  items: ['is01','is02','is03'], mekanikId: 'mk1', cabang: 'snt', total: 425000, foto: 1, catatan: '-' },
  { id: 'sh010', tgl: '2026-05-11', vhId: 'vh013', km: 82400,  items: ['is01','is02','is05'], mekanikId: 'mk3', cabang: 'cld', total: 985000, foto: 3, catatan: 'Kampas rem mulai tipis sebelah kanan.' },
  { id: 'sh011', tgl: '2026-05-10', vhId: 'vh007', km: 196500, items: ['is01','is02','is06'], mekanikId: 'mk1', cabang: 'snt', total: 1180000,foto: 2, catatan: 'Kampas rem blkg + servis berkala.' },
  { id: 'sh012', tgl: '2026-05-08', vhId: 'vh018', km: 285000, items: ['is01','is02','is13'], mekanikId: 'mk5', cabang: 'bks', total: 825000, foto: 2, catatan: 'Oli + radiator merembes sedikit, diawasi.' },
  { id: 'sh013', tgl: '2026-05-05', vhId: 'vh003', km: 124800, items: ['is01','is02','is04'], mekanikId: 'mk1', cabang: 'snt', total: 925000, foto: 3, catatan: 'Tune up komplit, idle stabil.' },
  { id: 'sh014', tgl: '2026-05-02', vhId: 'vh014', km: 18500,  items: ['is01','is02','is11'], mekanikId: 'mk6', cabang: 'bks', total: 285000, foto: 2, catatan: 'Servis motor berkala.' },
  { id: 'sh015', tgl: '2026-04-28', vhId: 'vh006', km: 245000, items: ['is01','is02','is13'], mekanikId: 'mk5', cabang: 'bks', total: 925000, foto: 2, catatan: 'Servis L300, persiapan trip Bandung.' },
  { id: 'sh016', tgl: '2026-04-25', vhId: 'vh001', km: 86400,  items: ['is01','is02','is07'], mekanikId: 'mk1', cabang: 'snt', total: 825000, foto: 3, catatan: 'Spooring after ganti oli.' },
  { id: 'sh017', tgl: '2026-04-22', vhId: 'vh016', km: 8500,   items: ['is01','is02'],         mekanikId: 'mk6', cabang: 'cld', total: 165000, foto: 1, catatan: 'Servis pertama PCX setelah 5000km.' },
  { id: 'sh018', tgl: '2026-04-20', vhId: 'vh025', km: 115200, items: ['is01','is02','is15'], mekanikId: 'mk5', cabang: 'bks', total: 425000, foto: 1, catatan: '-' },
  { id: 'sh019', tgl: '2026-04-18', vhId: 'vh001', km: 82100,  items: ['is04','is11'],         mekanikId: 'mk1', cabang: 'snt', total: 685000, foto: 2, catatan: 'Tune up + busi.' },
  { id: 'sh020', tgl: '2026-04-15', vhId: 'vh010', km: 72800,  items: ['is01','is02'],         mekanikId: 'mk1', cabang: 'snt', total: 285000, foto: 1, catatan: '-' },
  { id: 'sh021', tgl: '2026-04-15', vhId: 'vh017', km: 164000, items: ['is01','is02','is07'], mekanikId: 'mk5', cabang: 'bks', total: 985000, foto: 2, catatan: 'Spooring + ganti oli.' },
  { id: 'sh022', tgl: '2026-04-12', vhId: 'vh021', km: 41000,  items: ['is01','is02','is11'], mekanikId: 'mk6', cabang: 'bks', total: 245000, foto: 1, catatan: 'Servis motor.' },
  { id: 'sh023', tgl: '2026-04-10', vhId: 'vh014', km: 16500,  items: ['is01','is02'],         mekanikId: 'mk6', cabang: 'bks', total: 145000, foto: 1, catatan: '-' },
  { id: 'sh024', tgl: '2026-04-08', vhId: 'vh020', km: 19500,  items: ['is01','is02','is03'], mekanikId: 'mk2', cabang: 'snt', total: 525000, foto: 2, catatan: 'Servis berkala 20rb km.' },
  { id: 'sh025', tgl: '2026-04-05', vhId: 'vh022', km: 99500,  items: ['is01','is02','is05','is06'], mekanikId: 'mk2', cabang: 'snt', total: 1685000,foto: 3, catatan: 'Servis berkala + kampas rem depan & belakang.' },
  { id: 'sh026', tgl: '2026-04-02', vhId: 'vh005', km: 89800,  items: ['is01','is02','is15'], mekanikId: 'mk1', cabang: 'snt', total: 485000, foto: 2, catatan: '-' },
  { id: 'sh027', tgl: '2026-03-30', vhId: 'vh012', km: 35200,  items: ['is01','is02','is07'], mekanikId: 'mk3', cabang: 'cld', total: 685000, foto: 2, catatan: 'Servis 35rb km + spooring.' },
  { id: 'sh028', tgl: '2026-03-20', vhId: 'vh008', km: 308000, items: ['is01','is02','is13'], mekanikId: 'mk5', cabang: 'bks', total: 685000, foto: 2, catatan: '-' },
  { id: 'sh029', tgl: '2026-03-15', vhId: 'vh002', km: 28500,  items: ['is01','is02','is03'], mekanikId: 'mk1', cabang: 'snt', total: 425000, foto: 1, catatan: 'Servis berkala 30rb km.' },
  { id: 'sh030', tgl: '2026-03-12', vhId: 'vh013', km: 78900,  items: ['is01','is02'],         mekanikId: 'mk3', cabang: 'cld', total: 285000, foto: 1, catatan: '-' },
];

// Booking servis (12 contoh; total 28 aktif)
const BOOKING_SERVIS = [
  { id: 'bk001', vhId: 'vh001', ownerId: 'ow01', tgl: '2026-05-21', jam: '10:00', durMin: 90,  cabang: 'snt', mekanikId: 'mk1', items: ['is01','is02','is07'], status: 'in_progress', estTotal: 825000, channel: 'app',     catatan: 'Tolong cek oli rembes di bawah mesin.' },
  { id: 'bk002', vhId: 'vh020', ownerId: 'ow12', tgl: '2026-05-21', jam: '11:00', durMin: 60,  cabang: 'snt', mekanikId: 'mk2', items: ['is04'],                 status: 'confirmed',   estTotal: 350000, channel: 'web',     catatan: '-' },
  { id: 'bk003', vhId: 'vh007', ownerId: 'ow03', tgl: '2026-05-21', jam: '13:00', durMin: 120, cabang: 'snt', mekanikId: 'mk1', items: ['is01','is02','is18'], status: 'confirmed',   estTotal: 825000, channel: 'wa',      catatan: 'Persiapan KIR akhir bulan.' },
  { id: 'bk004', vhId: 'vh012', ownerId: 'ow07', tgl: '2026-05-21', jam: '14:30', durMin: 90,  cabang: 'cld', mekanikId: 'mk3', items: ['is10'],                 status: 'confirmed',   estTotal: 525000, channel: 'app',     catatan: 'AC kurang dingin.' },
  { id: 'bk005', vhId: 'vh006', ownerId: 'ow03', tgl: '2026-05-22', jam: '08:30', durMin: 60,  cabang: 'snt', mekanikId: 'mk1', items: ['is01','is02'],         status: 'confirmed',   estTotal: 285000, channel: 'web',     catatan: '-' },
  { id: 'bk006', vhId: 'vh022', ownerId: 'ow14', tgl: '2026-05-22', jam: '10:00', durMin: 60,  cabang: 'snt', mekanikId: 'mk2', items: ['is01','is02'],         status: 'confirmed',   estTotal: 425000, channel: 'app',     catatan: '-' },
  { id: 'bk007', vhId: 'vh015', ownerId: 'ow10', tgl: '2026-05-22', jam: '13:00', durMin: 45,  cabang: 'snt', mekanikId: 'mk6', items: ['is01','is02','is11'], status: 'pending',     estTotal: 245000, channel: 'app',     catatan: 'Permintaan jam fleksibel.' },
  { id: 'bk008', vhId: 'vh003', ownerId: 'ow02', tgl: '2026-05-23', jam: '09:00', durMin: 90,  cabang: 'snt', mekanikId: 'mk1', items: ['is01','is02','is17'], status: 'confirmed',   estTotal: 685000, channel: 'wa',      catatan: 'Persiapan mudik akhir minggu depan.' },
  { id: 'bk009', vhId: 'vh009', ownerId: 'ow04', tgl: '2026-05-23', jam: '11:30', durMin: 60,  cabang: 'cld', mekanikId: 'mk3', items: ['is01','is02'],         status: 'confirmed',   estTotal: 285000, channel: 'app',     catatan: '-' },
  { id: 'bk010', vhId: 'vh019', ownerId: 'ow11', tgl: '2026-05-24', jam: '10:00', durMin: 120, cabang: 'cld', mekanikId: 'mk3', items: ['is01','is02','is05','is07'], status: 'confirmed', estTotal: 1185000,channel: 'web', catatan: 'Cek rem rada blong.' },
  { id: 'bk011', vhId: 'vh018', ownerId: 'ow09', tgl: '2026-05-25', jam: '08:00', durMin: 90,  cabang: 'bks', mekanikId: 'mk5', items: ['is01','is02','is18'], status: 'confirmed',   estTotal: 985000, channel: 'wa',      catatan: 'KIR Colt Diesel.' },
  { id: 'bk012', vhId: 'vh023', ownerId: 'ow16', tgl: '2026-05-25', jam: '11:00', durMin: 180, cabang: 'snt', mekanikId: 'mk1', items: ['is04','is05','is06','is15'], status: 'pending', estTotal: 1450000,channel: 'app', catatan: 'Service besar, lama nggak servis.' },
];

// Work order aktif (12)
const WORK_ORDER = [
  { id: 'wo001', no: 'WO-2026-0521-001', bkId: 'bk001', vhId: 'vh001', mekanikId: 'mk1', cabang: 'snt', mulai: '10:05', estSelesai: '11:35', progress: 65, status: 'in_progress', items: [{ id:'is01', done:true }, { id:'is02', done:true }, { id:'is07', done:false }], fotoBefore: 2, fotoAfter: 0, catatan: 'Oli mesin rembes konfirmed dari paking carter.' },
  { id: 'wo002', no: 'WO-2026-0521-002', bkId: null,    vhId: 'vh024', mekanikId: 'mk3', cabang: 'cld', mulai: '09:15', estSelesai: '10:45', progress: 80, status: 'in_progress', items: [{ id:'is01', done:true }, { id:'is02', done:true }, { id:'is11', done:true }], fotoBefore: 2, fotoAfter: 1, catatan: 'Walk-in customer.' },
  { id: 'wo003', no: 'WO-2026-0521-003', bkId: null,    vhId: 'vh021', mekanikId: 'mk6', cabang: 'bks', mulai: '09:30', estSelesai: '10:30', progress: 50, status: 'in_progress', items: [{ id:'is01', done:true }, { id:'is02', done:false }], fotoBefore: 1, fotoAfter: 0, catatan: '-' },
  { id: 'wo004', no: 'WO-2026-0520-014', bkId: null,    vhId: 'vh017', mekanikId: 'mk5', cabang: 'bks', mulai: '14:20', estSelesai: '17:00', progress: 100, status: 'qc',         items: [{ id:'is01', done:true }, { id:'is02', done:true }, { id:'is03', done:true }], fotoBefore: 2, fotoAfter: 2, catatan: 'Menunggu QC final.' },
];

// Doc reminder (STNK / pajak / asuransi / KIR) — computed
function _docReminders() {
  const list = [];
  KENDARAAN.forEach(k => {
    const owner = OWNER_BY_ID[k.ownerId];
    const push = (kind, label, exp) => {
      if (!exp) return;
      const d = daysToExpiry(exp);
      if (d <= 90) list.push({ id: `dr_${k.id}_${kind}`, kind, label, vhId: k.id, plat: k.plat, ownerId: k.ownerId, ownerName: owner?.name || '-', cabang: k.cabang, exp, daysLeft: d });
    };
    push('stnk',     'STNK',     k.stnkExp);
    push('pajak',    'Pajak Tahunan', k.pajakExp);
    push('asuransi', 'Asuransi', k.asuransiExp);
    push('kir',      'KIR',      k.kirExp);
  });
  return list.sort((a,b) => a.daysLeft - b.daysLeft);
}

// BBM/efisiensi sample (per kendaraan) — 12 bulan terakhir, km/L
const BBM_EFISIENSI = {
  vh001: [12.4, 12.1, 11.8, 11.5, 12.0, 12.3, 11.9, 11.6, 11.2, 10.8, 10.5, 10.2],
  vh002: [16.8, 17.0, 17.2, 16.9, 16.5, 16.7, 17.1, 16.8, 16.4, 16.6, 17.0, 16.9],
  vh006: [9.2, 9.0, 8.8, 8.5, 8.3, 8.6, 8.4, 8.2, 7.9, 7.7, 7.5, 7.3],
  vh022: [8.5, 8.4, 8.2, 8.0, 8.3, 8.5, 8.2, 8.0, 7.8, 7.6, 7.4, 7.2],
};

// Alerts (operasional)
const ALERTS = [
  { id: 'al1', kind: 'doc',     level: 'danger', msg: 'STNK Suzuki Carry B 8003 LJ — exp 28 Mei (7 hari)', vhId: 'vh008' },
  { id: 'al2', kind: 'service', level: 'danger', msg: 'Avanza B 1234 ABC sudah 4.300 km dari oli terakhir', vhId: 'vh001' },
  { id: 'al3', kind: 'stock',   level: 'warn',   msg: 'Aki GS NS40 stok 12, hampir di bawah minimum',       spId: 'sp08' },
  { id: 'al4', kind: 'booking', level: 'info',   msg: '2 booking pending menunggu konfirmasi mekanik' },
  { id: 'al5', kind: 'doc',     level: 'warn',   msg: 'KIR Mitsubishi L300 B 8001 LJ — exp 30 Juni (40 hari)', vhId: 'vh006' },
];

// KPI
const KPI = {
  reminderDue:      35,
  reminderUrgent:   8,
  bookingHariIni:   7,
  bookingMingguIni: 28,
  woAktif:          12,
  woSelesaiHariIni: 4,
  revenueHariIni:   4850000,
  revenueBulanIni:  185600000,
  kendaraanAktif:   180,
  customerAktif:    120,
  healthAvg:        78,
  dokumenExpSoon:   14,
};

// Revenue 14 hari
const REVENUE_14D = [
  { d: '08', v: 3850000 }, { d: '09', v: 4250000 }, { d: '10', v: 5120000 },
  { d: '11', v: 4680000 }, { d: '12', v: 3950000 }, { d: '13', v: 4420000 },
  { d: '14', v: 5680000 }, { d: '15', v: 6240000 }, { d: '16', v: 4850000 },
  { d: '17', v: 4120000 }, { d: '18', v: 5340000 }, { d: '19', v: 6580000 },
  { d: '20', v: 5920000 }, { d: '21', v: 4850000 },
];

// Aktivitas terkini
const ACTIVITY = [
  { id: 'ac01', t: '09:38', txt: 'WO-2026-0521-001 progress 65% — oli ganti, lanjut spooring',           tone: 'info' },
  { id: 'ac02', t: '09:25', txt: 'Booking baru: Honda Vario (B 2345 HIJ) — Sabtu 13:00',                  tone: 'success' },
  { id: 'ac03', t: '09:15', txt: 'WO-2026-0521-003 dimulai oleh Hendra (mekanik)',                        tone: 'info' },
  { id: 'ac04', t: '09:05', txt: 'Reminder WA terkirim ke 12 customer (STNK exp <30 hari)',               tone: 'info' },
  { id: 'ac05', t: '08:50', txt: 'Sparepart restock: Oli Shell HX7 +24 botol',                            tone: 'success' },
  { id: 'ac06', t: '08:30', txt: 'Walk-in: Suzuki XL7 B 7148 CDE — Bu Ratih, ganti aki',                  tone: 'info' },
  { id: 'ac07', t: '08:00', txt: 'Shift pagi dimulai — 6 mekanik standby',                                 tone: 'info' },
];

// ──────────────────────────────────────────────────────────────────
// Helpers
// ──────────────────────────────────────────────────────────────────
function rupiah(n, opts = { sym: true, compact: false }) {
  if (n == null || isNaN(n)) return '—';
  if (opts.compact) {
    if (n >= 1000000000) return (opts.sym ? 'Rp ' : '') + (n / 1000000000).toFixed(1) + ' M';
    if (n >= 1000000)    return (opts.sym ? 'Rp ' : '') + (n / 1000000).toFixed(1) + ' jt';
    if (n >= 1000)       return (opts.sym ? 'Rp ' : '') + (n / 1000).toFixed(0) + 'k';
  }
  const s = Math.round(n).toLocaleString('id-ID');
  return (opts.sym ? 'Rp ' : '') + s;
}

function formatDateID(iso) {
  if (!iso) return '—';
  const months = ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Agu','Sep','Okt','Nov','Des'];
  const [y, m, d] = iso.split('-').map(Number);
  return `${d} ${months[m - 1]} ${y}`;
}
function formatDateTimeID(iso, hhmm) {
  return `${formatDateID(iso)} • ${hhmm || ''}`.trim();
}

function periodLabel(id) {
  return PERIOD_LIST.find(p => p.id === id)?.label || id;
}

function daysToExpiry(iso) {
  if (!iso) return null;
  const d0 = new Date(TODAY + 'T00:00:00');
  const d1 = new Date(iso + 'T00:00:00');
  return Math.round((d1 - d0) / 86400000);
}
function dueLevel(days) {
  if (days == null) return 'unknown';
  if (days < 0)  return 'overdue';
  if (days <= 7) return 'danger';
  if (days <= 30) return 'warn';
  if (days <= 90) return 'soon';
  return 'ok';
}
function dueLabel(days) {
  if (days == null) return '—';
  if (days < 0)  return `Lewat ${Math.abs(days)} hari`;
  if (days === 0) return 'Hari ini';
  if (days === 1) return 'Besok';
  return `${days} hari lagi`;
}

// Component health → ratio 0..1.2 (>1 = lewat)
function componentRatio(vh, compId) {
  const def = HEALTH_COMP_BY_ID[compId];
  const cur = vh.components?.[compId];
  if (!def || !cur) return null;
  const rKm = cur.km / def.intervalKm;
  const rMo = cur.mo / def.intervalMo;
  return Math.max(rKm, rMo);
}
function componentTone(ratio) {
  if (ratio == null) return 'unknown';
  if (ratio >= 1.0)  return 'danger';
  if (ratio >= 0.85) return 'warn';
  return 'good';
}
function healthScore(vh) {
  if (!vh.components) return null;
  const ratios = HEALTH_COMPONENTS
    .map(c => componentRatio(vh, c.id))
    .filter(r => r != null);
  if (!ratios.length) return null;
  // setiap komponen kontribusi: good=100, warn=70, danger=30
  const sum = ratios.reduce((s, r) => {
    if (r >= 1.0)  return s + Math.max(20, 30 - (r - 1) * 50);
    if (r >= 0.85) return s + 70 - (r - 0.85) * 200;
    return s + 100 - r * 35;
  }, 0);
  return Math.round(sum / ratios.length);
}
function scoreTone(score) {
  if (score == null) return 'unknown';
  if (score >= 80) return 'good';
  if (score >= 60) return 'warn';
  return 'danger';
}
function scoreLabel(score) {
  if (score == null) return '—';
  if (score >= 80) return 'Sehat';
  if (score >= 60) return 'Perlu Perhatian';
  return 'Servis Segera';
}

// Reminder servis berkala — per kendaraan, item paling mendesak
function vehicleNextService(vh) {
  if (!vh.components) return null;
  let worst = null;
  HEALTH_COMPONENTS.forEach(c => {
    const r = componentRatio(vh, c.id);
    if (r == null) return;
    if (!worst || r > worst.ratio) worst = { compId: c.id, label: c.label, ratio: r };
  });
  return worst;
}

// Build reminder cards: gabungan komponen due + dokumen due
function buildReminders() {
  const list = [];
  KENDARAAN.forEach(vh => {
    const owner = OWNER_BY_ID[vh.ownerId];
    HEALTH_COMPONENTS.forEach(c => {
      const r = componentRatio(vh, c.id);
      if (r == null || r < 0.85) return;
      const tone = componentTone(r);
      list.push({
        id: `rm_${vh.id}_${c.id}`,
        kind: 'service',
        compId: c.id,
        icon: c.icon,
        title: `${c.label} — ${vh.merk} ${vh.model}`,
        sub: `${vh.plat} • ${owner?.name || '-'}`,
        vhId: vh.id, ownerId: vh.ownerId, cabang: vh.cabang,
        ratio: r, tone,
        progress: Math.min(100, Math.round(r * 100)),
      });
    });
  });
  // tambah doc
  _docReminders().forEach(d => {
    const tone = dueLevel(d.daysLeft) === 'overdue' || dueLevel(d.daysLeft) === 'danger' ? 'danger'
               : dueLevel(d.daysLeft) === 'warn' ? 'warn' : 'good';
    list.push({
      id: d.id,
      kind: 'doc',
      icon: d.kind === 'kir' ? 'clipboard' : d.kind === 'asuransi' ? 'shield' : 'document',
      title: `${d.label} — ${KENDARAAN_BY_ID[d.vhId]?.merk} ${KENDARAAN_BY_ID[d.vhId]?.model}`,
      sub: `${d.plat} • ${d.ownerName} • exp ${formatDateID(d.exp)}`,
      vhId: d.vhId, ownerId: d.ownerId, cabang: d.cabang,
      daysLeft: d.daysLeft, tone,
      progress: Math.max(0, Math.min(100, 100 - (d.daysLeft / 90) * 100)),
    });
  });
  return list.sort((a, b) => {
    const order = { danger: 0, warn: 1, good: 2, unknown: 3 };
    return (order[a.tone] - order[b.tone]) || (b.progress - a.progress);
  });
}

function ownerName(id) { return OWNER_BY_ID[id]?.name || '—'; }
function ownerVehicles(ownerId) { return KENDARAAN.filter(k => k.ownerId === ownerId); }
function cabangName(id) { return CABANG.find(c => c.id === id)?.name || id; }
function vehicleLabel(vh) { return `${vh.merk} ${vh.model} • ${vh.plat}`; }

function bookingStatusTone(s) {
  return { confirmed: 'info', in_progress: 'warning', done: 'success', pending: 'warning', cancelled: 'destructive' }[s] || 'muted';
}
function bookingStatusLabel(s) {
  return { confirmed: 'Confirmed', in_progress: 'Dikerjakan', done: 'Selesai', pending: 'Pending', cancelled: 'Batal' }[s] || s;
}
function channelLabel(ch) {
  return { app: 'App', web: 'Web', wa: 'WhatsApp', walkin: 'Walk-in', telp: 'Telpon' }[ch] || ch;
}

function bookingTotalItems(bk) {
  return (bk.items || []).reduce((s, id) => s + (ITEM_SERVIS_BY_ID[id]?.hargaJasa || 0), 0);
}

function stockLevel(sp) {
  if (sp.stok <= sp.min * 0.5) return 'low';
  if (sp.stok <= sp.min) return 'warn';
  return 'ok';
}
