/* ============================================================
   Venus WMS — Vinasse Management
   ------------------------------------------------------------
   React recreation of the Claude Design prototype, now wired to a
   live Cloudflare Worker + D1 backend.

   - DEMO mode  (config.js apiBase = "")  → built-in sample data,
     role switcher, no backend required.
   - LIVE mode  (apiBase set)             → login screen, then real
     data fetched from the API; role comes from the logged-in user.

   Inline-style strings from the prototype are preserved verbatim via
   the s() helper so the UI matches the mockup pixel-for-pixel.
   ============================================================ */

/* Parse a CSS declaration string into a React style object. */
function s(css) {
  const out = {};
  if (!css) return out;
  css.split(';').forEach((decl) => {
    const i = decl.indexOf(':');
    if (i < 0) return;
    const rawKey = decl.slice(0, i).trim();
    const val = decl.slice(i + 1).trim();
    if (!rawKey) return;
    const key = rawKey.startsWith('--') ? rawKey : rawKey.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
    out[key] = val;
  });
  return out;
}

const ICONS = {
  grid: ['M3 3h7v7H3z', 'M14 3h7v7h-7z', 'M14 14h7v7h-7z', 'M3 14h7v7H3z'],
  route: ['M6 19a3 3 0 1 0 0-6 3 3 0 0 0 0 6z', 'M18 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z', 'M9 16h6a3 3 0 0 0 3-3V8'],
  map: ['M9 3 3 6v15l6-3 6 3 6-3V3l-6 3-6-3z', 'M9 3v15', 'M15 6v15'],
  sprout: ['M7 20h10', 'M12 20v-9', 'M12 11c0-3-2.5-5.5-6-5.5 0 3 2.5 5.5 6 5.5z', 'M12 11c0-2.5 2-5 5.5-5 0 2.8-2.2 5-5.5 5z'],
  droplet: ['M12 3s6 6.5 6 10.5a6 6 0 0 1-12 0C6 9.5 12 3 12 3z'],
  bell: ['M6 9a6 6 0 0 1 12 0c0 7 3 8 3 8H3s3-1 3-8', 'M10.3 21a1.94 1.94 0 0 0 3.4 0'],
  file: ['M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z', 'M14 2v6h6', 'M8 13h8', 'M8 17h6'],
  users: ['M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2', 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8', 'M22 21v-2a4 4 0 0 0-3-3.87', 'M16 3.13a4 4 0 0 1 0 7.75'],
  settings: ['M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z', 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'],
  clock: ['M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z', 'M12 6v6l4 2'],
  leaf: ['M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.52-4.48 10-10 10z', 'M2 21c0-3 1.85-5.36 5.08-6'],
  warn: ['M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z', 'M12 9v4', 'M12 17h.01'],
  arrow: ['M5 12h14', 'M13 6l6 6-6 6'],
  search: ['M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z', 'M21 21l-4.3-4.3'],
  filter: ['M22 3H2l8 9.46V19l4 2v-8.54z'],
  download: ['M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4', 'M7 10l5 5 5-5', 'M12 15V3'],
  plus: ['M12 5v14', 'M5 12h14'],
  check: ['M20 6 9 17l-5-5'],
  x: ['M18 6 6 18', 'M6 6l12 12'],
  lock: ['M5 11h14v10H5z', 'M8 11V7a4 4 0 0 1 8 0v4'],
  logout: ['M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4', 'M16 17l5-5-5-5', 'M21 12H9'],
  factory: ['M2 20a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V9l-6 4V9l-6 4V5a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1z', 'M7 17h.01', 'M11 17h.01', 'M15 17h.01', 'M19 17h.01'],
  flask: ['M9 3h6', 'M10 3v6.5L5 18a2 2 0 0 0 1.8 3h10.4A2 2 0 0 0 19 18l-5-8.5V3', 'M7.5 15h9'],
  pump: ['M3 12h6', 'M9 8h4l3 3v6H9z', 'M16 11h3a2 2 0 0 1 2 2v0', 'M6 9v6'],
  tank: ['M4 7h16v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z', 'M4 7c0-2 4-3 8-3s8 1 8 3', 'M8 12h8'],
  antenna: ['M12 12v9', 'M8 16a5 5 0 0 1 0-7', 'M16 9a5 5 0 0 1 0 7', 'M5.6 18.4a9 9 0 0 1 0-12.8', 'M18.4 5.6a9 9 0 0 1 0 12.8'],
  shield: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z', 'M9 12l2 2 4-4'],
  eye: ['M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z', 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z'],
  wrench: ['M14.7 6.3a4 4 0 0 0-5 5L4 17l3 3 5.7-5.7a4 4 0 0 0 5-5l-2.5 2.5-2.5-.7-.7-2.5z'],
  sliders: ['M4 21v-7', 'M4 10V3', 'M12 21v-9', 'M12 8V3', 'M20 21v-5', 'M20 12V3', 'M1 14h6', 'M9 8h6', 'M17 16h6'],
  layers: ['M12 2 2 7l10 5 10-5-10-5z', 'M2 17l10 5 10-5', 'M2 12l10 5 10-5'],
  pin: ['M12 21s7-6 7-11a7 7 0 0 0-14 0c0 5 7 11 7 11z', 'M12 12a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z'],
  building: ['M3 21h18', 'M5 21V7l8-4v18', 'M19 21V11l-6-3', 'M9 9h.01', 'M9 12h.01', 'M9 15h.01', 'M13 12h.01', 'M13 15h.01'],
  doc: ['M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z', 'M14 2v6h6'],
};

// ============================================================
//  GIS dashboard — sample data (per the ChatGIS design / โครงการกระจายน้ำ)
// ============================================================
const GIS_KPIS = [
  { label: 'เกษตรกรทั้งหมด', value: '1,248', unit: 'ราย', trend: '▲ เพิ่มขึ้น 32 ราย (2.63%)', trendColor: 'var(--green-600)', accent: 'var(--green-600)', icon: 'users' },
  { label: 'แปลงเพาะปลูกทั้งหมด', value: '2,563', unit: 'แปลง', trend: '▲ เพิ่มขึ้น 68 แปลง (2.73%)', trendColor: 'var(--green-600)', accent: 'var(--co-uba)', icon: 'sprout' },
  { label: 'พื้นที่เพาะปลูกทั้งหมด', value: '18,642.75', unit: 'ไร่', trend: '▲ เพิ่มขึ้น 412.50 ไร่ (2.26%)', trendColor: 'var(--green-600)', accent: 'var(--co-usa)', icon: 'leaf' },
  { label: 'ปริมาณการใช้น้ำ (สะสม)', value: '3.45', unit: 'ล้าน ลบ.ม.', trend: 'คิดเป็น 48.62% ของแผนจัดสรร', trendColor: 'var(--blue-500)', accent: 'var(--blue-500)', icon: 'droplet' },
  { label: 'สถานะเอกสารครบถ้วน', value: '87.35%', unit: '', trend: '▲ เพิ่มขึ้น 3.21%', trendColor: 'var(--green-600)', accent: 'var(--amber-500)', icon: 'doc' },
];
const GIS_AREA = [
  { label: 'ทำนา', rai: '10,245.50', pct: '54.95%', v: 10245.5, color: '#1F8A56' },
  { label: 'พืชไร่', rai: '5,324.10', pct: '28.57%', v: 5324.1, color: '#C8901F' },
  { label: 'พืชผัก', rai: '2,156.75', pct: '11.57%', v: 2156.75, color: '#7E5BB0' },
  { label: 'ไม้ผล/ไม้ยืนต้น', rai: '916.40', pct: '4.91%', v: 916.4, color: '#C2412D' },
];
const GIS_WATER = [
  { label: 'ใช้น้ำปกติ', n: '1,593', pct: '62.12%', v: 1593, color: '#1F8A56' },
  { label: 'ใช้น้ำน้อย', n: '472', pct: '18.41%', v: 472, color: '#9AD05A' },
  { label: 'ใช้น้ำมาก', n: '310', pct: '12.09%', v: 310, color: '#E8A317' },
  { label: 'ใช้น้ำเกินแผน', n: '116', pct: '4.53%', v: 116, color: '#C2412D' },
  { label: 'ยังไม่ใช้น้ำ', n: '72', pct: '2.81%', v: 72, color: '#A1A9A4' },
];
const GIS_TOP_TAMBON = [
  ['1', 'ตำบลหนองไผ่', '4,256.50', '586'],
  ['2', 'ตำบลท่าเสา', '3,842.25', '512'],
  ['3', 'ตำบลวังยาว', '3,256.75', '436'],
  ['4', 'ตำบลหนองหว้า', '2,856.50', '398'],
  ['5', 'ตำบลทุ่งนางาม', '2,430.75', '331'],
];
const GIS_CROPS = [
  { icon: 'sprout', th: 'ข้าวนาปี', rai: '10,245.50', pct: '54.95%' },
  { icon: 'sprout', th: 'อ้อย', rai: '3,256.10', pct: '17.46%' },
  { icon: 'sprout', th: 'มันสำปะหลัง', rai: '2,068.75', pct: '11.08%' },
  { icon: 'sprout', th: 'ข้าวโพด', rai: '1,987.40', pct: '10.66%' },
  { icon: 'sprout', th: 'พืชผัก', rai: '1,085.00', pct: '5.81%' },
];
const GIS_MONTHLY = [['ธ.ค.', 0.45], ['ม.ค.', 0.52], ['ก.พ.', 0.60], ['มี.ค.', 0.68], ['เม.ย.', 0.72], ['พ.ค.', 0.83], ['มิ.ย.', 0.65]];
// plot water-usage status → marker colour (also used in legend)
const GIS_STATUS_COLOR = { normal: '#1F8A56', low: '#9AD05A', high: '#E8A317', over: '#C2412D', none: '#A1A9A4' };
const GIS_STATUS_TH = { normal: 'ใช้น้ำปกติ', low: 'ใช้น้ำน้อย', high: 'ใช้น้ำมาก', over: 'ใช้น้ำเกินแผน', none: 'ยังไม่ใช้น้ำ' };

function icon(name, size, color) {
  const paths = ICONS[name] || [];
  return React.createElement(
    'svg',
    { width: size || 18, height: size || 18, viewBox: '0 0 24 24', fill: 'none', stroke: color || 'currentColor', strokeWidth: 1.75, strokeLinecap: 'round', strokeLinejoin: 'round', style: { display: 'block' } },
    paths.map((d, i) => React.createElement('path', { key: i, d }))
  );
}
const ic = (n, sz, c) => icon(n, sz, c);

// ---- demo dataset (used when no API is configured) ----
const GROUPS = [
  { code: 'KL', no: 1, company: 'UBE', th: 'ขี้เหล็ก–นาดู่', farmers: 72, nozzles: 76, plots: 55, rai: 1946, color: '#2A66B0', lat: [15.111, 15.145], lon: [105.022, 105.041] },
  { code: 'NS', no: 2, company: 'UBE', th: 'โนนสุขสันต์', farmers: 100, nozzles: 206, plots: 129, rai: 1814, color: '#1F8A56', lat: [15.130, 15.145], lon: [105.001, 105.042] },
  { code: 'NM', no: 3, company: 'UBE', th: 'นางาม–หนองสิมมา', farmers: 116, nozzles: 189, plots: 134, rai: 1717, color: '#C8901F', lat: [15.148, 15.163], lon: [105.004, 105.036] },
  { code: 'NP', no: 4, company: 'UBS', th: 'หนองแปน', farmers: 97, nozzles: 185, plots: 121, rai: 1826, color: '#7E5BB0', lat: [15.150, 15.183], lon: [105.030, 105.055] },
  { code: 'PK', no: 5, company: 'UBS', th: 'ป่าข่า', farmers: 53, nozzles: 124, plots: 66, rai: 1405, color: '#C2412D', lat: [15.186, 15.210], lon: [105.046, 105.057] },
  { code: 'NN', no: 6, company: 'UBS', th: 'หนองเลิงนา', farmers: 32, nozzles: 81, plots: 40, rai: 783, color: '#2596A8', lat: [15.199, 15.215], lon: [105.025, 105.044] },
  { code: 'HP', no: 7, company: 'UBS', th: 'ห้วยแสนพราน', farmers: 33, nozzles: 73, plots: 43, rai: 622, color: '#D98A1F', lat: [15.191, 15.216], lon: [105.054, 105.066] },
];
const CROPS = ['มันสำปะหลัง', 'หญ้าเนเปียร์', 'อ้อย', 'ยางพารา', 'ข้าว'];
const FNAMES = ['สมชาย โนนสุข', 'บุญมี ศรีนวล', 'ประสิทธิ์ ทองคำ', 'กนกพร ใจดี', 'วิรัตน์ ป่าข่า', 'มาลี สุขสันต์', 'อุดม นาดู่', 'พิมพ์ใจ หนองแปน', 'เสนาะ บุญเรือง', 'ทองพูน แก้วมณี', 'ชัยวัฒน์ ภูทอง', 'สมหญิง คำผง', 'บุญส่ง วงศ์ใหญ่', 'สุดา จันทร์ดี', 'ประยูร สีดา', 'อรทัย พิมพา'];
const VILLAGES = ['บ.โนนสุขสันต์', 'บ.หนองสิมมาใต้', 'บ.นางาม', 'บ.หนองแปน', 'บ.ป่าข่า', 'บ.หนองเลิงนา', 'บ.ห้วยแสนพราน', 'บ.นาดู่'];
const ST_KEYS = ['flowing', 'active', 'active', 'maintenance', 'active', 'pending', 'active', 'flowing'];
const DEMO_FARMERS = FNAMES.map((nm, i) => {
  const g = GROUPS[i % 7];
  return { no: String(i + 1).padStart(3, '0'), name: (i % 3 === 0 ? 'นาย' : i % 3 === 1 ? 'นาง' : 'น.ส.') + nm, group: g.code, groupColor: g.color, village: VILLAGES[i % VILLAGES.length], nozzle: 'WM-' + g.code + '-' + String((i * 7 % 190) + 1).padStart(3, '0'), rai: (8 + i * 3 % 34), crop: CROPS[i % CROPS.length], st: ST_KEYS[i % ST_KEYS.length] };
});
const DEMO_NOZZLES = (() => {
  const out = [];
  GROUPS.forEach((g) => {
    const per = Math.min(16, Math.round(g.nozzles / 9) + 6);
    let seed = g.no * 77;
    const rnd = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; };
    for (let i = 0; i < per; i++) {
      const r = rnd();
      const st = r < 0.33 ? 'flowing' : r < 0.86 ? 'active' : r < 0.95 ? 'maintenance' : 'inactive';
      out.push({ id: 'WM-' + g.code + '-' + String(i + 1).padStart(3, '0'), group: g.code, groupColor: g.color, owner: (i % 2 ? 'นาง' : 'นาย') + ' ' + ['ก', 'บ', 'ส', 'ม', 'ว', 'ท'][i % 6] + '. ' + g.th.slice(0, 4), area: (6 + (i * 5) % 28), status: st });
    }
  });
  return out;
})();
const DEMO_ALERTS = [
  { icon: 'warn', title: 'แรงดันต่ำ — WM-NS-087', body: 'หัวจ่ายกลุ่ม 2 รายงานแรงดัน 1.3 bar ต่ำกว่าเกณฑ์ 1.5 bar', tag: 'แรงดัน', time: '08:42', tone: 'warning' },
  { icon: 'wrench', title: 'กำหนดซ่อมบำรุง — WM-KL-034', body: 'นัดหมายทีมช่างเข้าตรวจสอบหัวจ่าย 17 มิ.ย. 2568', tag: 'ซ่อมบำรุง', time: '07:15', tone: 'info' },
  { icon: 'tank', title: 'Buffer Tank RT-03 เต็ม 92%', body: 'ปริมาณน้ำใน Buffer Tank กลุ่ม NM ใกล้เต็ม แนะนำเร่งจ่ายน้ำ', tag: 'ระดับน้ำ', time: '06:50', tone: 'warning' },
  { icon: 'droplet', title: 'หัวจ่ายชำรุด — WM-PK-112', body: 'ตรวจพบการรั่วซึมที่ข้อต่อ ต้องเปลี่ยนอุปกรณ์', tag: 'ชำรุด', time: 'อังคาร', tone: 'danger' },
  { icon: 'sprout', title: 'คำขอลงทะเบียนใหม่ 3 ราย', body: 'เกษตรกรกลุ่ม HP ขอลงทะเบียนเข้าร่วมโครงการ รออนุมัติ', tag: 'ลงทะเบียน', time: 'จันทร์', tone: 'info' },
  { icon: 'check', title: 'รอบน้ำ 7 เสร็จสมบูรณ์', body: 'ส่งน้ำ 46,200 ลบ.ม. ครอบคลุม 9,840 ไร่ ตามแผน', tag: 'รอบน้ำ', time: '8 มิ.ย.', tone: 'success' },
];
const DEMO_USERS = [
  { name: 'สมชาย วงศ์ใหญ่', email: 'somchai@ubonbioethanol.com', role: 'admin', groups: 'ทุกกลุ่ม', av: '#14704A', initial: 'ส', active: true },
  { name: 'ปิยะ ศรีสุข', email: 'piya@ubonbioethanol.com', role: 'operator', groups: 'KL, NS', av: '#2A66B0', initial: 'ป', active: true },
  { name: 'กนกพร ทองดี', email: 'kanok@ubonbioethanol.com', role: 'operator', groups: 'NM', av: '#C8901F', initial: 'ก', active: true },
  { name: 'ธนา ภูมิใจ', email: 'thana@ubonbioethanol.com', role: 'operator', groups: 'NP, PK', av: '#7E5BB0', initial: 'ธ', active: true },
  { name: 'วิไล จันทร์เพ็ญ', email: 'wilai@ubonbioethanol.com', role: 'viewer', groups: 'ดูอย่างเดียว', av: '#6F7A74', initial: 'ว', active: true },
  { name: 'EVP สิ่งแวดล้อม', email: 'evp.env@ubonbioethanol.com', role: 'viewer', groups: 'อนุมัติพื้นที่', av: '#5E8C2A', initial: 'E', active: false },
];

// ---- source plant + plot-footprint geometry ----
// UBE Ubon Bio Ethanol plant — the Vinasse source (ต้นทางน้ำ)
const UBE_PLANT = { lat: 15.155799693477078, lon: 105.04116703342021, name: 'UBE · โรงงานอุบลไบโอเอทานอล', sub: 'ต้นทางน้ำ Vinasse · สถานีสูบหลัก' };
const RAI_M2 = 1600; // 1 ไร่ = 1,600 ตร.ม.
const STATUS_TH = { flowing: 'กำลังส่งน้ำ', active: 'พร้อมใช้งาน', maintenance: 'ซ่อมบำรุง', inactive: 'ไม่ใช้งาน' };
// axis-aligned square footprint (in lat/lng) centred on a point, sized by rai
function squareAround(lat, lon, rai) {
  const side = Math.sqrt(Math.max(Number(rai) || 1, 0.5) * RAI_M2); // metres
  const half = side / 2;
  const dLat = half / 111320;
  const dLon = half / (111320 * Math.cos((lat * Math.PI) / 180));
  return [[lat - dLat, lon - dLon], [lat - dLat, lon + dLon], [lat + dLat, lon + dLon], [lat + dLat, lon - dLon]];
}

// ---- live-data adapters ----
const ROLE_AV = { admin: '#14704A', operator: '#2A66B0', viewer: '#6F7A74' };
const ALERT_ICON = { pressure_low: 'warn', maintenance_due: 'wrench', flow_abnormal: 'tank', leak: 'droplet', offline: 'sprout' };
const ALERT_TAG = { pressure_low: 'แรงดัน', maintenance_due: 'ซ่อมบำรุง', flow_abnormal: 'ระดับน้ำ', leak: 'ชำรุด', offline: 'ลงทะเบียน' };
const SEV_TONE = { critical: 'danger', warning: 'warning', info: 'info' };

function adaptGroups(rows) {
  return rows.map((g) => ({
    id: g.id, no: g.id, code: g.code, company: g.company, th: g.name_th,
    nozzles: g.total_nozzles || 0, rai: g.total_rai || 0, farmers: g.farmer_count || 0,
    color: g.color_hex || '#1F8A56', lat: [g.lat_min, g.lat_max], lon: [g.lon_min, g.lon_max],
    flowing: g.flowing || 0, ready: g.ready || 0, maintenance: g.maintenance || 0,
  }));
}
function groupColorMap(groups) { const m = {}; groups.forEach((g) => { m[g.code] = g.color; }); return m; }
function adaptFarmers(rows, gColor) {
  return rows.map((f, i) => ({
    no: f.farmer_code ? f.farmer_code.split('-').pop() : String(i + 1).padStart(3, '0'),
    name: ((f.prefix || '') + (f.first_name_th || '') + ' ' + (f.last_name_th || '')).trim(),
    group: f.group_code, groupColor: f.color_hex || gColor[f.group_code] || '#6F7A74',
    village: f.village || '-', nozzle: f.nozzle_code || '—', rai: f.total_rai,
    crop: f.main_crop || '-', st: f.status || 'active',
  }));
}
function adaptNozzles(rows, gColor) {
  return rows.map((nz) => ({
    id: nz.nozzle_code, group: nz.group_code, groupColor: nz.color_hex || gColor[nz.group_code] || '#6F7A74',
    owner: nz.farmer_name || '—', area: nz.area_rai, status: nz.status,
    lat: nz.lat, lon: nz.lon,
  }));
}
function adaptAlerts(rows) {
  return rows.map((a) => ({
    icon: ALERT_ICON[a.alert_type] || 'bell', title: a.title, body: a.message || '',
    tag: ALERT_TAG[a.alert_type] || a.alert_type, tone: SEV_TONE[a.severity] || 'info',
    time: (a.created_at || '').slice(11, 16) || (a.created_at || '').slice(0, 10),
  }));
}
function adaptUsers(rows) {
  return rows.map((u) => {
    let groups = '-';
    const gi = u.group_ids;
    if (gi === 'all' || gi === '"all"') groups = 'ทุกกลุ่ม';
    else { try { const a = JSON.parse(gi); groups = Array.isArray(a) && a.length ? 'กลุ่ม ' + a.join(', ') : 'ดูอย่างเดียว'; } catch (e) { groups = gi || '-'; } }
    let rawGroupIds = [];
    try { const a = JSON.parse(u.group_ids); rawGroupIds = Array.isArray(a) ? a : []; } catch (e) {}
    return { name: u.full_name_th || u.username, username: u.username, email: u.email, role: u.role, groups, av: ROLE_AV[u.role] || '#6F7A74', initial: (u.full_name_th || u.username || '?').trim().charAt(0), active: !!u.is_active, rawId: u.id, rawActive: u.is_active, rawGroupIds };
  });
}

// ============================================================
//  Login screen (LIVE mode, when not authenticated)
// ============================================================
function LoginView({ onSubmit, error, busy }) {
  const [u, setU] = React.useState('');
  const [p, setP] = React.useState('');
  const submit = (e) => { e.preventDefault(); if (u && p) onSubmit(u, p); };
  const field = 'width:100%;padding:10px 12px;font-family:inherit;font-size:13px;color:var(--text-strong);background:var(--surface-card);border:1px solid var(--border-field);border-radius:var(--radius-md);outline:none;';
  return (
    <div style={s('min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--surface-app);font-family:var(--font-sans);padding:20px;')}>
      <form onSubmit={submit} style={s('width:100%;max-width:380px;background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-xl);box-shadow:var(--shadow-lg);padding:30px 28px;')}>
        <div style={s('display:flex;align-items:center;gap:12px;margin-bottom:22px;')}>
          <img src="assets/ube-logo.png" alt="UBE" style={s('height:40px;width:auto;object-fit:contain;')} />
          <div style={s('width:1px;height:32px;background:var(--border-subtle);')}></div>
          <div style={s('line-height:1.25;')}>
            <div style={s('font-size:15px;font-weight:700;color:var(--text-strong);')}>Venus WMS</div>
            <div style={s('font-size:10.5px;color:var(--text-muted);')}>ระบบบริหารจัดการน้ำ Vinasse</div>
          </div>
        </div>
        <div className="ube-eyebrow" style={s('margin-bottom:4px;')}>เข้าสู่ระบบ</div>
        <h1 style={s('font-size:20px;font-weight:600;color:var(--text-strong);margin-bottom:18px;')}>ลงชื่อเข้าใช้งาน</h1>
        <div style={s('display:flex;flex-direction:column;gap:6px;margin-bottom:13px;')}>
          <label style={s('font-size:11.5px;font-weight:500;color:var(--text-muted);')}>ชื่อผู้ใช้ <span style={s('color:var(--text-faint);')}>Username</span></label>
          <input value={u} onChange={(e) => setU(e.target.value)} autoFocus style={s(field)} />
        </div>
        <div style={s('display:flex;flex-direction:column;gap:6px;margin-bottom:18px;')}>
          <label style={s('font-size:11.5px;font-weight:500;color:var(--text-muted);')}>รหัสผ่าน <span style={s('color:var(--text-faint);')}>Password</span></label>
          <input type="password" value={p} onChange={(e) => setP(e.target.value)} style={s(field)} />
        </div>
        {error ? <div style={s('font-size:12px;color:var(--red-600);background:var(--red-50);border:1px solid var(--red-500);border-radius:var(--radius-md);padding:8px 11px;margin-bottom:14px;')}>{error}</div> : null}
        <button type="submit" disabled={busy} style={s('width:100%;padding:11px;border:none;border-radius:var(--radius-md);font-family:inherit;font-size:13.5px;font-weight:600;cursor:pointer;background:var(--color-primary);color:#fff;opacity:' + (busy ? '0.7' : '1') + ';')}>{busy ? 'กำลังเข้าสู่ระบบ…' : 'เข้าสู่ระบบ'}</button>
        <div style={s('margin-top:16px;font-size:11px;color:var(--text-faint);text-align:center;line-height:1.5;')}>UBE Ubon Bio Ethanol Group · Venus Vinasse Water Management</div>
      </form>
    </div>
  );
}

// ============================================================
//  Main app
// ============================================================
class App extends React.Component {
  constructor(props) {
    super(props);
    const cfg = window.VENUS_CONFIG || {};
    this.apiBase = (cfg.apiBase || '').replace(/\/+$/, '');
    let me = null;
    try { me = JSON.parse(localStorage.getItem('venus_me') || 'null'); } catch (e) {}
    this.state = {
      role: 'admin', page: 'dashboard', mapFilter: 'ALL', nozGroup: 'ALL', nozStatus: 'ALL',
      userTab: 'allUsers', search: '', time: '--:--:--', toast: '',
      // live mode
      token: localStorage.getItem('venus_token') || '', me,
      live: null, loading: false, loadErr: '', loginErr: '', loggingIn: false,
      // user modal
      userModal: null, userSaving: false, userDelConfirm: null, kpiModal: null, barDetail: null, barDrillDown: null, barDrillSearch: '', farmerDetail: null, nozzleDetail: null, alertDetail: null,
      // add-plot (footprint) flow
      addMode: false, pendingPlot: null, plotForm: { group: '', owner: '', area: '5', crop: 'มันสำปะหลัง' }, plotSaving: false,
      // GIS dashboard map
      gisBasemap: 'satellite',
    };
    this.groups = GROUPS.map((g) => ({ ...g, id: g.no }));
    this.demoExtra = []; // plots added in DEMO mode
  }

  isLive() { return !!this.apiBase; }

  componentDidMount() {
    this.tick = setInterval(() => {
      const d = new Date();
      const p = (x) => String(x).padStart(2, '0');
      this.setState({ time: p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds()) });
    }, 1000);
    if (this.isLive()) {
      if (this.state.token) this.loadData();
    } else {
      this.tryInitMap();
    }
  }
  componentWillUnmount() { if (this.tick) clearInterval(this.tick); clearTimeout(this._tt); }

  // ---- API ----
  async api(path, opts = {}) {
    const headers = { 'Content-Type': 'application/json', ...(opts.headers || {}) };
    if (this.state.token) headers.Authorization = 'Bearer ' + this.state.token;
    const res = await fetch(this.apiBase + path, { ...opts, headers });
    if (res.status === 401) { this.logout(); throw new Error('เซสชันหมดอายุ — กรุณาเข้าสู่ระบบใหม่'); }
    if (!res.ok) { let e = {}; try { e = await res.json(); } catch (x) {} throw new Error(e.error || ('HTTP ' + res.status)); }
    return res.json();
  }

  async doLogin(username, password) {
    this.setState({ loggingIn: true, loginErr: '' });
    try {
      const r = await fetch(this.apiBase + '/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) });
      const data = await r.json();
      if (!r.ok) throw new Error(data.error || 'เข้าสู่ระบบไม่สำเร็จ');
      localStorage.setItem('venus_token', data.token);
      localStorage.setItem('venus_me', JSON.stringify(data.user));
      this.setState({ token: data.token, me: data.user, loggingIn: false }, () => this.loadData());
    } catch (e) {
      this.setState({ loginErr: e.message === 'Invalid credentials' ? 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง' : e.message, loggingIn: false });
    }
  }

  logout() {
    localStorage.removeItem('venus_token');
    localStorage.removeItem('venus_me');
    if (this.map) { try { this.map.remove(); } catch (e) {} this.map = null; }
    this.setState({ token: '', me: null, live: null, page: 'dashboard' });
  }

  async loadData() {
    this.setState({ loading: true, loadErr: '' });
    try {
      const isAdmin = this.state.me && this.state.me.role === 'admin';
      const reqs = [
        this.api('/api/dashboard'), this.api('/api/farmers?limit=503'),
        this.api('/api/nozzles'), this.api('/api/alerts'), this.api('/api/rounds'),
        isAdmin ? this.api('/api/users').catch(() => []) : Promise.resolve([]),
      ];
      const [dash, farmersR, nozzles, alerts, rounds, usersR] = await Promise.all(reqs);
      this.setState({ live: { groups: dash.groups || [], kpi: dash.kpi || {}, farmers: farmersR.data || [], nozzles: nozzles || [], alerts: alerts || [], rounds: rounds || [], users: usersR || [] }, loading: false });
      this.groups = adaptGroups(dash.groups || []);
      if (this.map) { try { this.map.remove(); } catch (e) {} this.map = null; }
      setTimeout(() => this.tryInitMap(), 60);
    } catch (e) {
      this.setState({ loadErr: e.message, loading: false });
    }
  }

  // ---- map ----
  tryInitMap() {
    if (!window.L) { this._t = (this._t || 0) + 1; if (this._t < 60) setTimeout(() => this.tryInitMap(), 120); return; }
    if (this.map || !document.getElementById('vinasseMap')) { if (!this.map) setTimeout(() => this.tryInitMap(), 120); return; }
    this.initMap();
  }
  initMap() {
    const L = window.L;
    const map = L.map('vinasseMap', { zoomControl: true, attributionControl: false });
    L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { maxZoom: 19, subdomains: 'abcd' }).addTo(map);
    this.map = map; this.layers = {};
    const gColor = {}; this.groups.forEach((g) => { gColor[g.code] = g.color; this.layers[g.code] = L.layerGroup().addTo(map); });
    this._gColor = gColor;

    // per-plot footprints (polygons sized by area)
    this.plotData().forEach((pl) => this.drawPlot(pl));

    // pipeline trunk + branches from the UBE plant
    this.pipeLayer = L.layerGroup().addTo(map);
    this.drawPipeline();

    // fit to the plant + all group bounds
    const pts = [[UBE_PLANT.lat, UBE_PLANT.lon]];
    this.groups.forEach((g) => { if (g.lat[0] != null) { pts.push([g.lat[0], g.lon[0]]); pts.push([g.lat[1], g.lon[1]]); } });
    try { map.fitBounds(L.latLngBounds(pts).pad(0.08)); } catch (e) { map.setView([15.13, 105.02], 11); }

    // click-to-add
    map.on('click', (e) => this.onMapClick(e));
    if (this.state.addMode) map.getContainer().style.cursor = 'crosshair';
  }

  // plots to draw: live nozzles (real coords+area) or procedural demo, plus demo additions
  plotData() {
    if (this.isLive() && this.state.live) {
      return (this.state.live.nozzles || []).filter((n) => n.lat != null && n.lon != null)
        .map((n) => ({ lat: n.lat, lon: n.lon, area: n.area_rai || 4, status: n.status, code: n.nozzle_code, group: n.group_code, owner: n.farmer_name || '' }));
    }
    const out = [];
    this.groups.forEach((g) => {
      if (g.lat[0] == null) return;
      const n = Math.max(9, Math.round(g.nozzles / 7));
      let seed = g.no * 1000;
      const rnd = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; };
      for (let i = 0; i < n; i++) {
        const lat = g.lat[0] + rnd() * (g.lat[1] - g.lat[0]);
        const lon = g.lon[0] + rnd() * (g.lon[1] - g.lon[0]);
        const r = rnd();
        const status = r < 0.34 ? 'flowing' : r < 0.9 ? 'active' : r < 0.96 ? 'maintenance' : 'inactive';
        out.push({ lat, lon, area: 6 + Math.floor(rnd() * 30), status, code: 'WM-' + g.code + '-' + String(i + 1).padStart(3, '0'), group: g.code, owner: '' });
      }
    });
    return out.concat(this.demoExtra);
  }

  drawPlot(pl) {
    const L = window.L; const lg = this.layers[pl.group]; if (!lg) return;
    const color = (this._gColor && this._gColor[pl.group]) || '#1F8A56';
    const fillOp = pl.status === 'flowing' ? 0.55 : pl.status === 'inactive' ? 0.16 : 0.34;
    const stroke = pl.status === 'maintenance' ? '#C8901F' : pl.status === 'inactive' ? '#A1A9A4' : color;
    L.polygon(squareAround(pl.lat, pl.lon, pl.area), { color: stroke, weight: 1, fillColor: color, fillOpacity: fillOp })
      .addTo(lg)
      .bindPopup('<b>' + pl.code + '</b><br>กลุ่ม ' + pl.group + (pl.owner ? ' · ' + pl.owner : '') + '<br>พื้นที่ ' + pl.area + ' ไร่<br>' + (STATUS_TH[pl.status] || pl.status));
  }

  drawPipeline() {
    const L = window.L; const lay = this.pipeLayer;
    const plant = [UBE_PLANT.lat, UBE_PLANT.lon];
    const stations = this.groups.filter((g) => g.lat[0] != null).map((g) => ({ g, c: [(g.lat[0] + g.lat[1]) / 2, (g.lon[0] + g.lon[1]) / 2] }));
    if (!stations.length) return;
    const cen = [stations.reduce((a, s) => a + s.c[0], 0) / stations.length, stations.reduce((a, s) => a + s.c[1], 0) / stations.length];
    // main trunk: plant -> manifold (thick line + animated-looking flow dashes)
    L.polyline([plant, cen], { color: '#0F5739', weight: 5, opacity: 0.85 }).addTo(lay).bindPopup('ท่อส่งหลัก (Main trunk) · จากโรงงาน UBE');
    L.polyline([plant, cen], { color: '#74BD96', weight: 2, opacity: 0.95, dashArray: '2 9' }).addTo(lay);
    // branches: manifold -> each group station
    stations.forEach((s) => {
      L.polyline([cen, s.c], { color: '#2A66B0', weight: 3, opacity: 0.6, dashArray: '7 6' }).addTo(lay).bindPopup('ท่อแยกกลุ่ม ' + s.g.code + ' · ' + s.g.th);
      L.circleMarker(s.c, { radius: 7, color: '#14704A', weight: 2.5, fillColor: '#ffffff', fillOpacity: 1 }).addTo(this.layers[s.g.code]).bindPopup('<b>สถานี/Buffer · ' + s.g.code + '</b><br>กลุ่ม ' + s.g.no + ' · ' + s.g.th);
    });
    L.circleMarker(cen, { radius: 6, color: '#0F5739', weight: 2, fillColor: '#14704A', fillOpacity: 1 }).addTo(lay).bindPopup('<b>จุดแยกท่อหลัก</b><br>Main manifold');
    L.circleMarker(plant, { radius: 11, color: '#0F5739', weight: 3, fillColor: '#C8901F', fillOpacity: 1 }).addTo(lay).bindPopup('<b>' + UBE_PLANT.name + '</b><br>' + UBE_PLANT.sub);
  }

  onMapClick(e) {
    if (!this.state.addMode) return;
    const L = window.L;
    if (this._tmpMarker) { try { this.map.removeLayer(this._tmpMarker); } catch (x) {} }
    this._tmpMarker = L.circleMarker(e.latlng, { radius: 9, color: '#14704A', weight: 2, fillColor: '#45A572', fillOpacity: 0.6 }).addTo(this.map);
    const def = this.state.mapFilter !== 'ALL' ? this.state.mapFilter : (this.groups[0] && this.groups[0].code) || '';
    this.setState({ pendingPlot: { lat: e.latlng.lat, lon: e.latlng.lng }, plotForm: { group: def, owner: '', area: '5', crop: 'มันสำปะหลัง' } });
  }

  toggleAddMode() {
    const on = !this.state.addMode;
    this.setState({ addMode: on, pendingPlot: null });
    if (this.map) this.map.getContainer().style.cursor = on ? 'crosshair' : '';
    if (!on && this._tmpMarker) { try { this.map.removeLayer(this._tmpMarker); } catch (x) {} this._tmpMarker = null; }
    if (on) this.showToast('โหมดเพิ่มพื้นที่ — คลิกตำแหน่งบนแผนที่');
  }

  closePlotModal() {
    if (this._tmpMarker) { try { this.map.removeLayer(this._tmpMarker); } catch (x) {} this._tmpMarker = null; }
    this.setState({ pendingPlot: null });
  }

  nextNozzleCode(groupCode) {
    let max = 0;
    const scan = (arr, get) => (arr || []).forEach((x) => { const c = get(x); if (c && c.indexOf('WM-' + groupCode + '-') === 0) { const num = parseInt(c.split('-').pop(), 10); if (num > max) max = num; } });
    if (this.isLive() && this.state.live) scan(this.state.live.nozzles, (n) => n.nozzle_code);
    else scan(this.plotData(), (n) => n.code);
    return 'WM-' + groupCode + '-' + String(max + 1).padStart(3, '0');
  }

  async doAddPlot() {
    const { pendingPlot, plotForm } = this.state;
    if (!pendingPlot || !plotForm.group) { this.showToast('เลือกกลุ่มและตำแหน่งก่อน'); return; }
    const area = Number(plotForm.area) || 0;
    if (area <= 0) { this.showToast('กรอกพื้นที่ (ไร่) ให้ถูกต้อง'); return; }
    const grp = this.groups.find((g) => g.code === plotForm.group);
    const code = this.nextNozzleCode(plotForm.group);
    if (this.isLive()) {
      this.setState({ plotSaving: true });
      try {
        if (!grp) { this.setState({ plotSaving: false }); this.showToast('ไม่พบกลุ่มที่เลือก'); return; }
        const notes = plotForm.owner ? ('เจ้าของ: ' + plotForm.owner + (plotForm.crop ? ' · ' + plotForm.crop : '')) : (plotForm.crop || null);
        await this.api('/api/nozzles', { method: 'POST', body: JSON.stringify({ nozzle_code: code, group_id: grp.id, farmer_id: null, lat: pendingPlot.lat, lon: pendingPlot.lon, area_rai: area, install_date: null, pipe_diameter_mm: 50, status: 'active', notes }) });
        if (this._tmpMarker) { try { this.map.removeLayer(this._tmpMarker); } catch (x) {} this._tmpMarker = null; }
        this.setState({ plotSaving: false, addMode: false, pendingPlot: null });
        if (this.map) this.map.getContainer().style.cursor = '';
        this.showToast('เพิ่มพื้นที่ ' + code + ' แล้ว');
        this.loadData();
      } catch (e) {
        this.setState({ plotSaving: false });
        this.showToast('บันทึกไม่สำเร็จ: ' + e.message);
      }
    } else {
      const pl = { lat: pendingPlot.lat, lon: pendingPlot.lon, area, status: 'active', code, group: plotForm.group, owner: plotForm.owner };
      this.demoExtra.push(pl);
      this.drawPlot(pl);
      if (this._tmpMarker) { try { this.map.removeLayer(this._tmpMarker); } catch (x) {} this._tmpMarker = null; }
      this.setState({ addMode: false, pendingPlot: null });
      if (this.map) this.map.getContainer().style.cursor = '';
      this.showToast('เพิ่มพื้นที่ ' + code + ' แล้ว (เดโม)');
    }
  }

  // ---- GIS dashboard map ----
  tryInitGisMap() {
    if (!window.L) { this._tg = (this._tg || 0) + 1; if (this._tg < 60) setTimeout(() => this.tryInitGisMap(), 120); return; }
    if (this.gisMap || !document.getElementById('gisMap')) { if (!this.gisMap) setTimeout(() => this.tryInitGisMap(), 120); return; }
    this.initGisMap();
  }
  initGisMap() {
    const L = window.L;
    const map = L.map('gisMap', { zoomControl: true, attributionControl: false }).setView([15.146, 105.018], 13);
    this.gisMap = map;
    this.gisBases = {
      street: L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { maxZoom: 19, subdomains: 'abcd' }),
      satellite: L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { maxZoom: 19 }),
    };
    this.gisLabels = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', { maxZoom: 19 });
    this.applyGisBase(this.state.gisBasemap || 'satellite');

    // ----- administrative boundaries -----
    const TAMBONS = [
      { name: 'ต.ท่าเสา', poly: [[15.165, 104.999], [15.169, 105.020], [15.151, 105.022], [15.149, 104.999]] },
      { name: 'ต.วังยาว', poly: [[15.149, 105.000], [15.151, 105.026], [15.127, 105.030], [15.123, 105.004]] },
      { name: 'ต.หนองไผ่', poly: [[15.151, 105.023], [15.166, 105.046], [15.140, 105.047], [15.135, 105.025]] },
    ];
    TAMBONS.forEach((t) => {
      L.polygon(t.poly, { color: '#ffffff', weight: 1.6, dashArray: '7 5', fill: false, opacity: 0.9 }).addTo(map);
      L.marker([t.poly[0][0], t.poly[0][1]], { icon: L.divIcon({ className: '', html: `<span style="font:600 11px var(--font-sans);color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.8);white-space:nowrap;">${t.name}</span>`, iconSize: [0, 0] }) }).addTo(map);
    });

    // ----- pipe network -----
    const MAIN = [[15.172, 105.012], [15.160, 105.014], [15.150, 105.018], [15.140, 105.026], [15.128, 105.030], [15.118, 105.030]];
    const SEC = [[[15.160, 105.014], [15.158, 105.001], [15.150, 104.998]], [[15.150, 105.018], [15.153, 105.034], [15.159, 105.041]], [[15.140, 105.026], [15.134, 105.040]]];
    const TER = [[[15.150, 105.018], [15.146, 105.009]], [[15.128, 105.030], [15.123, 105.041]], [[15.158, 105.001], [15.151, 105.000]]];
    L.polyline(MAIN, { color: '#1D4ED8', weight: 5, opacity: 0.95 }).addTo(map).bindPopup('ท่อส่งน้ำหลัก');
    SEC.forEach((p) => L.polyline(p, { color: '#3B82F6', weight: 3, opacity: 0.9 }).addTo(map).bindPopup('ท่อส่งน้ำรอง'));
    TER.forEach((p) => L.polyline(p, { color: '#60A5FA', weight: 2, dashArray: '5 5', opacity: 0.9 }).addTo(map).bindPopup('ท่อส่งน้ำซอย'));
    // pipe junction dots
    MAIN.forEach((ll) => L.circleMarker(ll, { radius: 3.5, color: '#1D4ED8', weight: 2, fillColor: '#fff', fillOpacity: 1 }).addTo(map));

    // ----- pump stations / structures -----
    const stIcon = (bg, glyph, sz) => L.divIcon({ className: '', html: `<div style="width:${sz}px;height:${sz}px;border-radius:7px;background:${bg};border:2px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;color:#fff;font-size:${sz - 14}px;">${glyph}</div>`, iconSize: [sz, sz], iconAnchor: [sz / 2, sz / 2] });
    const STATIONS = [
      { ll: [15.172, 105.012], name: 'สถานีสูบน้ำหลัก', bg: '#14704A', glyph: '⌂', sz: 30 },
      { ll: [15.160, 105.014], name: 'สถานีสูบน้ำย่อย 1', bg: '#1F8A56', glyph: '⌂', sz: 26 },
      { ll: [15.140, 105.026], name: 'สถานีสูบน้ำย่อย 2', bg: '#1F8A56', glyph: '⌂', sz: 26 },
      { ll: [15.118, 105.030], name: 'อ่างเก็บน้ำห้วยหว้า', bg: '#2A66B0', glyph: '◆', sz: 26 },
    ];
    STATIONS.forEach((s) => L.marker(s.ll, { icon: stIcon(s.bg, s.glyph, s.sz), zIndexOffset: 1000 }).addTo(map).bindPopup('<b>' + s.name + '</b>'));

    // ----- farmer plots (coloured by water-usage status) -----
    this.gisPlots().forEach((pl) => {
      L.circleMarker(pl.ll, { radius: 6, color: '#ffffff', weight: 1.5, fillColor: GIS_STATUS_COLOR[pl.status], fillOpacity: 0.95 })
        .addTo(map)
        .bindPopup('<b>แปลง ' + pl.code + '</b><br>' + pl.tambon + '<br>' + pl.crop + ' · ' + pl.rai + ' ไร่<br>' + GIS_STATUS_TH[pl.status]);
    });
  }
  gisPlots() {
    if (this._gisPlots) return this._gisPlots;
    const TAMBON = ['ต.ท่าเสา', 'ต.วังยาว', 'ต.หนองไผ่'];
    const CROP = ['ข้าวนาปี', 'อ้อย', 'มันสำปะหลัง', 'ข้าวโพด', 'พืชผัก'];
    const out = [];
    let seed = 4321;
    const rnd = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; };
    for (let i = 0; i < 58; i++) {
      const lat = 15.122 + rnd() * 0.046;
      const lon = 105.000 + rnd() * 0.044;
      const r = rnd();
      const status = r < 0.62 ? 'normal' : r < 0.80 ? 'low' : r < 0.92 ? 'high' : r < 0.965 ? 'over' : 'none';
      out.push({ ll: [lat, lon], status, code: 'P-' + String(i + 1).padStart(4, '0'), tambon: TAMBON[Math.floor(rnd() * 3)], crop: CROP[Math.floor(rnd() * CROP.length)], rai: (5 + Math.floor(rnd() * 40)) });
    }
    this._gisPlots = out;
    return out;
  }
  applyGisBase(key) {
    const map = this.gisMap; if (!map) return;
    Object.values(this.gisBases).forEach((l) => { if (map.hasLayer(l)) map.removeLayer(l); });
    if (map.hasLayer(this.gisLabels)) map.removeLayer(this.gisLabels);
    const baseKey = key === 'hybrid' ? 'satellite' : key;
    this.gisBases[baseKey].addTo(map);
    this.gisBases[baseKey].bringToBack();
    if (key === 'hybrid') this.gisLabels.addTo(map);
  }
  setGisBasemap(key) { this.setState({ gisBasemap: key }); this.applyGisBase(key); }

  go(page) {
    this.setState({ page });
    if (page === 'map') setTimeout(() => { if (this.map) this.map.invalidateSize(); else this.tryInitMap(); }, 80);
    if (page === 'gis') setTimeout(() => { if (this.gisMap) this.gisMap.invalidateSize(); else this.tryInitGisMap(); }, 80);
  }
  setRole(r) { this.setState({ role: r }); }
  setMapFilter(code) {
    this.setState({ mapFilter: code });
    if (this.map) {
      this.groups.forEach((g) => {
        const lg = this.layers[g.code]; if (!lg) return;
        if (code === 'ALL' || code === g.code) { if (!this.map.hasLayer(lg)) lg.addTo(this.map); }
        else if (this.map.hasLayer(lg)) this.map.removeLayer(lg);
      });
    }
  }
  showToast(msg) { this.setState({ toast: msg }); clearTimeout(this._tt); this._tt = setTimeout(() => this.setState({ toast: '' }), 2200); }

  openUserModal(user = null) {
    this.setState({
      userModal: user
        ? { id: user.rawId, username: user.username, full_name_th: user.name, email: user.email, role: user.role, group_ids: user.rawGroupIds || [], is_active: user.rawActive, password: '' }
        : { id: null, username: '', full_name_th: '', email: '', role: 'viewer', group_ids: [], is_active: 1, password: '' },
    });
  }

  async saveUser() {
    const m = this.state.userModal;
    if (!m) return;
    if (!m.username) { this.showToast('กรุณาใส่ชื่อผู้ใช้'); return; }
    if (!m.id && !m.password) { this.showToast('กรุณาใส่รหัสผ่าน'); return; }
    this.setState({ userSaving: true });
    try {
      const body = { username: m.username, full_name_th: m.full_name_th, email: m.email, role: m.role, group_ids: m.group_ids, is_active: m.is_active };
      if (m.password) body.password = m.password;
      if (m.id) {
        await this.api('/api/users/' + m.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
        this.showToast('แก้ไขผู้ใช้งานเรียบร้อย');
      } else {
        body.password = m.password;
        await this.api('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
        this.showToast('เพิ่มผู้ใช้งานเรียบร้อย');
      }
      this.setState({ userModal: null, userSaving: false });
      this.loadData();
    } catch (e) { this.showToast('เกิดข้อผิดพลาด: ' + e.message); this.setState({ userSaving: false }); }
  }

  async deleteUser(id) {
    try {
      await this.api('/api/users/' + id, { method: 'DELETE' });
      this.showToast('ลบผู้ใช้งานเรียบร้อย');
      this.setState({ userDelConfirm: null });
      this.loadData();
    } catch (e) { this.showToast('เกิดข้อผิดพลาด: ' + e.message); }
  }

  // pull together the dataset for the current mode
  dataset() {
    if (this.isLive() && this.state.live) {
      const L = this.state.live;
      const groups = adaptGroups(L.groups);
      const gColor = groupColorMap(groups);
      const round = (L.rounds || []).find((r) => r.status === 'active') || (L.rounds || [])[0] || null;
      return { groups, farmers: adaptFarmers(L.farmers, gColor), nozzles: adaptNozzles(L.nozzles, gColor), alerts: adaptAlerts(L.alerts), users: adaptUsers(L.users), kpi: L.kpi || {}, round };
    }
    return { groups: this.groups, farmers: DEMO_FARMERS, nozzles: DEMO_NOZZLES, alerts: DEMO_ALERTS, users: DEMO_USERS, kpi: null, round: null };
  }

  render() {
    // LIVE mode login gate
    if (this.isLive() && !this.state.token) {
      return <LoginView onSubmit={(u, p) => this.doLogin(u, p)} error={this.state.loginErr} busy={this.state.loggingIn} />;
    }

    const S = this.state;
    const live = this.isLive();
    const role = live ? (S.me && S.me.role) || 'viewer' : S.role;
    const isAdmin = role === 'admin';
    const canEdit = role !== 'viewer';

    const DS = this.dataset();
    this.groups = DS.groups; // keep map methods in sync
    const groups = DS.groups;

    // ---- KPI numbers (live or demo) ----
    let K;
    if (DS.kpi && Object.keys(DS.kpi).length) {
      const k = DS.kpi;
      const total = k.total_nozzles || 0;
      const flowing = k.nozzles_flowing || 0, ready = k.nozzles_ready || 0, maint = k.nozzles_maintenance || 0;
      const inactive = Math.max(0, total - flowing - ready - maint);
      const online = k.nozzles_online != null ? k.nozzles_online : total - inactive;
      K = {
        volume: DS.round && DS.round.actual_volume_m3 != null ? Math.round(DS.round.actual_volume_m3).toLocaleString() : '—',
        totalRai: Math.round(k.total_rai || 0).toLocaleString(),
        nozzlesOnline: online.toLocaleString(), nozzlesOnlinePct: total ? (online / total * 100).toFixed(1) : '0',
        totalNozzles: total, activeFarmers: k.active_farmers || 0, totalFarmers: k.total_farmers || 0,
        pendingFarmers: Math.max(0, (k.total_farmers || 0) - (k.active_farmers || 0)),
        round: k.current_round || (DS.round && DS.round.round_number) || '—',
        roundStart: DS.round && DS.round.start_date ? DS.round.start_date : '',
        flowing, ready, maint, inactive,
      };
    } else {
      K = { volume: '48,620', totalRai: '10,113', nozzlesOnline: '891', nozzlesOnlinePct: '95.4', totalNozzles: 934, activeFarmers: 468, totalFarmers: 503, pendingFarmers: 35, round: 8, roundStart: '', flowing: 312, ready: 579, maint: 30, inactive: 13 };
    }

    // ---- role meta / topbar ----
    const DEMO_ROLE_META = {
      admin: { name: 'สมชาย วงศ์ใหญ่', en: 'Administrator', av: '#14704A', initial: 'ส' },
      operator: { name: 'ปิยะ ศรีสุข', en: 'Operator', av: '#2A66B0', initial: 'ป' },
      viewer: { name: 'วิไล จันทร์เพ็ญ', en: 'Viewer', av: '#6F7A74', initial: 'ว' },
    };
    const roleEnMap = { admin: 'Administrator', operator: 'Operator', viewer: 'Viewer' };
    const roleMeta = live
      ? { name: (S.me && (S.me.full_name_th || S.me.username)) || 'ผู้ใช้', en: roleEnMap[role] || role, av: ROLE_AV[role] || '#6F7A74', initial: ((S.me && (S.me.full_name_th || S.me.username)) || '?').trim().charAt(0) }
      : DEMO_ROLE_META[role];

    const roleTabs = [['admin', 'Admin', 'shield'], ['operator', 'Operator', 'sliders'], ['viewer', 'Viewer', 'eye']].map(([id, label, ico]) => {
      const on = S.role === id;
      return { id, label, icon: ic(ico, 13, on ? '#fff' : 'var(--text-muted)'), onClick: () => this.setRole(id), style: `display:flex;align-items:center;gap:5px;padding:5px 11px;border:none;border-radius:var(--radius-pill);font-family:inherit;font-size:11.5px;font-weight:${on ? '600' : '500'};cursor:pointer;background:${on ? 'var(--color-primary)' : 'transparent'};color:${on ? '#fff' : 'var(--text-muted)'};` };
    });

    // ---- nav ----
    const navDef = [
      { label: 'ภาพรวม', items: [
        { id: 'dashboard', icon: 'grid', label: 'แดชบอร์ด', labelEn: 'Dashboard' },
        { id: 'gis', icon: 'layers', label: 'GIS แผนที่', labelEn: 'GIS Map' },
        { id: 'map', icon: 'map', label: 'แผนที่ระบบท่อ', labelEn: 'Pipe Network Map' },
      ] },
      { label: 'การจัดการ', items: [
        { id: 'farmers', icon: 'sprout', label: 'เกษตรกร', labelEn: 'Farmers', badge: String(K.totalFarmers) },
        { id: 'nozzles', icon: 'droplet', label: 'หัวจ่ายน้ำ', labelEn: 'Nozzles', badge: String(K.totalNozzles) },
      ] },
      { label: 'รายงาน', items: [
        { id: 'reports', icon: 'file', label: 'รายงานผล', labelEn: 'Reports' },
        { id: 'alerts', icon: 'bell', label: 'แจ้งเตือน', labelEn: 'Alerts', badge: String(DS.alerts.length || '') },
      ] },
    ];
    if (isAdmin) navDef.push({ label: 'ผู้ดูแลระบบ', items: [
      { id: 'users', icon: 'users', label: 'จัดการผู้ใช้', labelEn: 'User Management' },
      { id: 'settings', icon: 'settings', label: 'ตั้งค่าระบบ', labelEn: 'System Settings' },
    ] });
    const navSections = navDef.map((sec) => ({
      label: sec.label,
      items: sec.items.map((it) => {
        const on = S.page === it.id;
        return { id: it.id, label: it.label, labelEn: it.labelEn, badge: it.badge || '', icon: ic(it.icon, 17, on ? 'var(--green-600)' : 'var(--text-muted)'), iconColor: on ? 'var(--green-600)' : 'var(--text-muted)', onClick: () => this.go(it.id), badgeStyle: `font-family:var(--font-mono);font-size:10px;font-weight:600;padding:1px 7px;border-radius:var(--radius-pill);background:${on ? 'var(--green-100)' : 'var(--surface-sunken)'};color:${on ? 'var(--green-700)' : 'var(--text-muted)'};`, style: `display:flex;align-items:center;gap:11px;width:100%;text-align:left;padding:9px 10px;margin-bottom:2px;border:none;border-radius:var(--radius-md);font-family:inherit;font-size:12.5px;font-weight:${on ? '600' : '500'};cursor:pointer;background:${on ? 'var(--green-50)' : 'transparent'};color:${on ? 'var(--green-700)' : 'var(--text-body)'};box-shadow:${on ? 'inset 2px 0 0 var(--green-600)' : 'none'};` };
      }),
    }));

    // ---- page heads ----
    const heads = {
      dashboard: ['ภาพรวมระบบ', 'แดชบอร์ด', 'System overview · Venus Vinasse'],
      gis: ['ภาพรวม', 'ระบบสารสนเทศภูมิศาสตร์ (GIS)', 'โครงการกระจายน้ำ · ข้อมูลภาพรวมแนวท่อและแปลงเกษตรกร'],
      map: ['แผนที่', 'แผนที่ระบบท่อกระจายน้ำ', 'Pipe network · ' + K.totalNozzles + ' nozzles across ' + groups.length + ' groups'],
      farmers: ['การจัดการ', 'ทะเบียนเกษตรกร', 'Farmer registry · ' + K.totalFarmers + ' records'],
      nozzles: ['การจัดการ', 'หัวจ่ายน้ำ', 'Distribution nozzles · ' + K.totalNozzles + ' heads'],
      reports: ['รายงาน', 'รายงานและสรุปผล', 'Reports & exports'],
      alerts: ['รายงาน', 'การแจ้งเตือนและปัญหา', 'Alerts & incidents'],
      users: ['ผู้ดูแลระบบ', 'จัดการผู้ใช้งาน', 'User management & roles'],
      settings: ['ผู้ดูแลระบบ', 'ตั้งค่าระบบ', 'System configuration'],
    };
    const head = heads[S.page] || heads.dashboard;

    // ---- page actions ----
    const btnP = 'display:flex;align-items:center;gap:6px;padding:8px 13px;border:none;border-radius:var(--radius-md);font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;background:var(--color-primary);color:#fff;';
    const btnS = 'display:flex;align-items:center;gap:6px;padding:8px 13px;border:1px solid var(--border-default);border-radius:var(--radius-md);font-family:inherit;font-size:12.5px;font-weight:500;cursor:pointer;background:var(--surface-card);color:var(--text-body);';
    let pageActions = [];
    const exportBtn = (label) => ({ label, icon: ic('download', 15), onClick: () => this.showToast('กำลังสร้างไฟล์ ' + label + '…'), style: btnS });
    const addBtn = (label, tip) => ({ label, icon: ic('plus', 15, '#fff'), onClick: () => this.showToast(tip), style: btnP });
    if (S.page === 'dashboard') pageActions = [exportBtn('สรุป PDF')];
    if (S.page === 'map' && canEdit) {
      const addOn = S.addMode;
      const btnActive = 'display:flex;align-items:center;gap:6px;padding:8px 13px;border:1px solid var(--amber-status-600);border-radius:var(--radius-md);font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;background:var(--amber-status-50);color:var(--amber-status-600);';
      pageActions = [exportBtn('KML'), { label: addOn ? 'กำลังเพิ่ม… (คลิกแผนที่)' : 'เพิ่มพื้นที่', icon: ic(addOn ? 'x' : 'plus', 15, addOn ? 'var(--amber-status-600)' : '#fff'), onClick: () => this.toggleAddMode(), style: addOn ? btnActive : btnP }];
    }
    if (S.page === 'farmers') { pageActions = [exportBtn('Excel')]; if (canEdit) pageActions.push(addBtn('เพิ่มเกษตรกร', 'เปิดฟอร์มลงทะเบียนเกษตรกร')); }
    if (S.page === 'nozzles' && canEdit) pageActions = [addBtn('เพิ่มหัวจ่าย', 'เปิดฟอร์มเพิ่มหัวจ่ายน้ำ')];
    if (S.page === 'alerts') pageActions = [{ label: 'ทำเครื่องหมายอ่านทั้งหมด', icon: ic('check', 15), onClick: () => this.showToast('ทำเครื่องหมายอ่านแล้ว'), style: btnS }];
    if (S.page === 'users' && isAdmin) pageActions = [{ label: 'เพิ่มผู้ใช้', icon: ic('plus', 15, '#fff'), onClick: () => this.openUserModal(), style: btnP }];
    if (S.page === 'settings' && isAdmin) pageActions = [{ label: 'บันทึก', icon: ic('check', 15, '#fff'), onClick: () => this.showToast('บันทึกการตั้งค่าแล้ว'), style: btnP }];

    // ---- which page ----
    const pages = ['dashboard', 'gis', 'map', 'farmers', 'nozzles', 'reports', 'alerts', 'users', 'settings'];
    const show = {}; pages.forEach((p) => { show[p] = S.page === p ? 'flex' : 'none'; });
    show.userList = S.userTab === 'allUsers' ? 'block' : 'none';
    show.userRoles = S.userTab === 'roles' ? 'block' : 'none';

    // ---- KPIs ----
    const kpis = [
      { label: 'น้ำ Vinasse ส่งแล้ว', value: K.volume, unit: 'ลบ.ม. / รอบนี้', trend: '▲ 12%', trendColor: 'var(--green-600)', accent: 'var(--co-usa)', icon: ic('droplet', 17) },
      { label: 'พื้นที่ได้รับน้ำ', value: K.totalRai, unit: 'ไร่', trend: '▲ 3%', trendColor: 'var(--green-600)', accent: 'var(--green-600)', icon: ic('sprout', 17) },
      { label: 'หัวจ่ายออนไลน์', value: K.nozzlesOnline, unit: 'จาก ' + K.totalNozzles + ' (' + K.nozzlesOnlinePct + '%)', trend: '▼ 2 หัว', trendColor: 'var(--red-500)', accent: 'var(--blue-500)', icon: ic('droplet', 17) },
      { label: 'เกษตรกรใช้น้ำ', value: String(K.activeFarmers), unit: 'จาก ' + K.totalFarmers + ' (' + (K.totalFarmers ? Math.round(K.activeFarmers / K.totalFarmers * 100) : 0) + '%)', trend: '▲ 5 ราย', trendColor: 'var(--green-600)', accent: 'var(--amber-500)', icon: ic('sprout', 17) },
      { label: 'รอบน้ำปัจจุบัน', value: String(K.round), unit: K.roundStart ? 'เริ่ม ' + K.roundStart : 'กำลังดำเนินการ', trend: 'กำลังส่ง', trendColor: 'var(--co-usa)', accent: 'var(--co-uba)', icon: ic('route', 17) },
    ];

    // ---- pipeline (illustrative) ----
    const pipeRaw = [
      { icon: 'factory', name: 'โรงงาน UBE', sub: 'แหล่งผลิต Vinasse', val: '180,000 m³/ปี', active: true },
      { icon: 'flask', name: 'บำบัดน้ำ', sub: 'Anaerobic + Evaporation', val: 'pH 6.8–7.2', active: true },
      { icon: 'pump', name: 'สถานีสูบหลัก', sub: 'Main Pumping', val: '3.2 bar', active: true },
      { icon: 'tank', name: 'Buffer Tanks', sub: groups.length + ' จุดพักน้ำ', val: '72% เต็ม', active: false },
      { icon: 'antenna', name: 'สถานีกระจาย', sub: groups.length + ' กลุ่ม', val: K.totalNozzles + ' หัวจ่าย', active: false },
      { icon: 'sprout', name: 'แปลงเกษตร', sub: 'Fertigation', val: K.totalRai + ' ไร่', active: false },
    ];
    const pipeline = pipeRaw.map((p, i) => ({ icon: ic(p.icon, 24, p.active ? 'var(--green-600)' : 'var(--text-muted)'), name: p.name, sub: p.sub, val: p.val, bg: p.active ? 'var(--green-50)' : 'var(--surface-card)', border: p.active ? 'var(--green-200)' : 'var(--border-subtle)', iconColor: p.active ? 'var(--green-600)' : 'var(--text-muted)', valColor: p.active ? 'var(--green-700)' : 'var(--text-muted)', arrow: i < pipeRaw.length - 1, arrowColor: i < 2 ? 'var(--green-500)' : 'var(--border-strong)' }));

    // ---- monthly bars (illustrative) ----
    const months = [['ต.ค.', 38200], ['พ.ย.', 41500], ['ธ.ค.', 36800], ['ม.ค.', 33400], ['ก.พ.', 29800], ['มี.ค.', 35600], ['เม.ย.', 42100], ['พ.ค.', 45300], ['มิ.ย.', 48620], ['ก.ค.', 44100], ['ส.ค.', 39700], ['ก.ย.', 43200]];
    const maxV = 52000, baseY = 178, chartH = 150, slot = 560 / 12;
    const monthlyBars = months.map((m, i) => { const h = (m[1] / maxV) * chartH; const w = slot * 0.56; const x = i * slot + (slot - w) / 2; return { x: x.toFixed(1), y: (baseY - h).toFixed(1), w: w.toFixed(1), h: h.toFixed(1), cx: (x + w / 2).toFixed(1), label: m[0], color: m[0] === 'มิ.ย.' ? 'var(--co-usa)' : 'var(--green-400)', rawVal: m[1] }; });
    const gridLines = [0, 13000, 26000, 39000, 52000].map((v) => ({ y: (baseY - (v / maxV) * chartH).toFixed(1), ty: (baseY - (v / maxV) * chartH - 3).toFixed(1), label: (v / 1000) + 'k' }));

    // ---- donut (live or demo) ----
    const nozzleStatus = [
      { label: 'กำลังส่งน้ำ', v: K.flowing, color: '#1F8A56' },
      { label: 'พร้อมใช้งาน', v: K.ready, color: '#74BD96' },
      { label: 'ซ่อมบำรุง', v: K.maint, color: '#C8901F' },
      { label: 'ไม่ใช้งาน', v: K.inactive, color: '#A1A9A4' },
    ];
    const total = nozzleStatus.reduce((a, b) => a + b.v, 0) || 1;
    const C = 2 * Math.PI * 56; let acc = 0;
    const donut = nozzleStatus.map((st) => { const frac = st.v / total; const seg = { color: st.color, dash: (frac * C - 2).toFixed(1) + ' ' + (C - (frac * C - 2)).toFixed(1), offset: (-acc * C).toFixed(1) }; acc += frac; return seg; });

    // ---- GIS dashboard derived (static) ----
    const gisDonut = (arr, r) => { const tot = arr.reduce((a, b) => a + b.v, 0) || 1; const cc = 2 * Math.PI * r; let ac = 0; return arr.map((sg) => { const frac = sg.v / tot; const out = { color: sg.color, dash: (frac * cc - 2).toFixed(1) + ' ' + (cc - (frac * cc - 2)).toFixed(1), offset: (-ac * cc).toFixed(1) }; ac += frac; return out; }); };
    const gisAreaDonut = gisDonut(GIS_AREA, 54);
    const gisWaterDonut = gisDonut(GIS_WATER, 54);
    const gMax = 0.9, gBaseY = 148, gH = 116, gSlot = 430 / GIS_MONTHLY.length;
    const gisBars = GIS_MONTHLY.map((m, i) => { const h = (m[1] / gMax) * gH; const w = gSlot * 0.46; const x = i * gSlot + (gSlot - w) / 2; return { x: x.toFixed(1), y: (gBaseY - h).toFixed(1), w: w.toFixed(1), h: h.toFixed(1), cx: (x + w / 2).toFixed(1), ty: (gBaseY - h - 5).toFixed(1), label: m[0], val: m[1].toFixed(2), hi: m[0] === 'พ.ค.' }; });
    const gisLegend = [
      { title: 'แนวท่อโครงการ', items: [{ kind: 'line', color: '#1D4ED8', w: 4, label: 'ท่อส่งน้ำหลัก' }, { kind: 'line', color: '#3B82F6', w: 2.5, label: 'ท่อส่งน้ำรอง' }, { kind: 'line', color: '#60A5FA', w: 2, dash: true, label: 'ท่อส่งน้ำซอย' }] },
      { title: 'สถานีสูบน้ำ / อาคาร', items: [{ kind: 'box', color: '#14704A', label: 'สถานีสูบน้ำหลัก' }, { kind: 'box', color: '#1F8A56', label: 'สถานีสูบน้ำย่อย' }, { kind: 'box', color: '#2A66B0', label: 'ตึก/อาคารประกอบ' }] },
      { title: 'จุดแปลงเกษตรกร (สถานะการใช้น้ำ)', items: GIS_WATER.map((w) => ({ kind: 'dot', color: w.color, label: w.label })) },
      { title: 'ขอบเขตการปกครอง', items: [{ kind: 'line', color: '#94A3B8', w: 1.5, dash: true, label: 'ขอบเขตตำบล' }, { kind: 'line', color: '#94A3B8', w: 1.5, dot: true, label: 'ขอบเขตหมู่บ้าน' }] },
    ];
    const gisToday = [['ใช้น้ำวันนี้', '15,430', 'ลบ.ม.', 'var(--blue-500)'], ['ใช้น้ำสะสม', '3,452,100', 'ลบ.ม.', 'var(--green-600)'], ['แผนจัดสรร', '7,100,000', 'ลบ.ม.', 'var(--amber-status-600)']];

    // ---- area bars ----
    const maxRai = Math.max(2000, ...groups.map((g) => g.rai || 0));
    const areaBars = groups.map((g) => ({ code: g.code, th: g.th.length > 6 ? g.th.slice(0, 6) : g.th, rai: Math.round(g.rai).toLocaleString(), w: ((g.rai / maxRai) * 100).toFixed(1) + '%', color: g.color }));

    // ---- crops (illustrative) ----
    const cropsRaw = [['มันสำปะหลัง', 248], ['หญ้าเนเปียร์', 142], ['อ้อย', 96], ['ยางพารา', 54], ['ข้าว', 32], ['ผลไม้/อื่นๆ', 16]];
    const maxCrop = 260;
    const cropColors = ['var(--green-600)', 'var(--green-400)', 'var(--co-uba)', 'var(--amber-400)', 'var(--amber-300)', 'var(--neutral-400)'];
    const crops = cropsRaw.map((c, i) => ({ th: c[0], plots: c[1], w: ((c[1] / maxCrop) * 100).toFixed(1) + '%', color: cropColors[i] }));

    // ---- map chips & list ----
    const chipBase = (on) => `display:flex;align-items:center;gap:6px;padding:5px 11px;border:1px solid ${on ? 'var(--green-600)' : 'var(--border-subtle)'};border-radius:var(--radius-pill);font-family:inherit;font-size:11.5px;font-weight:${on ? '600' : '500'};cursor:pointer;background:${on ? 'var(--green-50)' : 'var(--surface-card)'};color:${on ? 'var(--green-700)' : 'var(--text-body)'};`;
    const mapChips = [{ code: 'ALL', label: 'ทั้งหมด', color: 'var(--neutral-400)' }].concat(groups.map((g) => ({ code: g.code, label: g.code, color: g.color }))).map((c) => { const on = S.mapFilter === c.code; return { label: c.label, dot: c.color, onClick: () => this.setMapFilter(c.code), style: chipBase(on) }; });
    const groupList = groups.map((g) => ({ no: g.no, code: g.code, th: g.th, company: g.company, nozzles: g.nozzles, rai: Math.round(g.rai).toLocaleString(), color: g.color, onClick: () => this.setMapFilter(g.code) }));
    const totalRaiAll = Math.round(groups.reduce((a, g) => a + (g.rai || 0), 0)).toLocaleString();

    // ---- farmers ----
    const stMap = {
      flowing: { bg: 'var(--status-info-bg)', fg: 'var(--status-info-fg)', label: 'กำลังรับน้ำ' },
      active: { bg: 'var(--status-success-bg)', fg: 'var(--status-success-fg)', label: 'ใช้งาน' },
      maintenance: { bg: 'var(--status-warning-bg)', fg: 'var(--status-warning-fg)', label: 'ซ่อมบำรุง' },
      pending: { bg: 'var(--status-neutral-bg)', fg: 'var(--status-neutral-fg)', label: 'รอลงทะเบียน' },
      inactive: { bg: 'var(--surface-sunken)', fg: 'var(--neutral-600)', label: 'ไม่ใช้งาน' },
    };
    const q = S.search.trim().toLowerCase();
    const farmersAll = DS.farmers.filter((f) => !q || (f.name || '').toLowerCase().includes(q) || (f.nozzle || '').toLowerCase().includes(q) || (f.village || '').toLowerCase().includes(q) || (f.group || '').toLowerCase().includes(q));
    const farmers = farmersAll.map((f) => { const st = stMap[f.st] || stMap.active; return { no: f.no, name: f.name, group: f.group, groupColor: f.groupColor, village: f.village, nozzle: f.nozzle, rai: f.rai, crop: f.crop, stBg: st.bg, stFg: st.fg, stLabel: st.label }; });
    const thStyle = 'padding:9px 14px;text-align:left;font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted);';
    const farmerCols = [['ลำดับ', ''], ['ชื่อ–สกุล', ''], ['กลุ่ม', ''], ['หมู่บ้าน', ''], ['รหัสหัวจ่าย', ''], ['ไร่', 'text-align:right;'], ['พืช', ''], ['สถานะ', '']].map((c) => ({ label: c[0], style: thStyle + c[1] }));
    const farmerStats = [
      { label: 'เกษตรกรทั้งหมด', value: String(K.totalFarmers), color: 'var(--text-strong)' },
      { label: 'ใช้น้ำแล้ว', value: String(K.activeFarmers), color: 'var(--green-600)' },
      { label: 'รอลงทะเบียน', value: String(K.pendingFarmers), color: 'var(--amber-status-600)' },
      { label: 'พื้นที่รวม', value: K.totalRai, color: 'var(--text-strong)' },
    ];

    // ---- nozzles ----
    const nozzleStatTiles = [
      { label: 'กำลังส่งน้ำ', value: String(K.flowing), color: '#1F8A56' },
      { label: 'พร้อมใช้งาน', value: String(K.ready), color: '#74BD96' },
      { label: 'ซ่อมบำรุง', value: String(K.maint), color: '#C8901F' },
      { label: 'ไม่ใช้งาน', value: String(K.inactive), color: '#A1A9A4' },
    ];
    const nozChip = (on) => `padding:5px 11px;border:1px solid ${on ? 'var(--green-600)' : 'var(--border-subtle)'};border-radius:var(--radius-pill);font-family:inherit;font-size:11.5px;font-weight:${on ? '600' : '500'};cursor:pointer;background:${on ? 'var(--green-50)' : 'var(--surface-card)'};color:${on ? 'var(--green-700)' : 'var(--text-body)'};display:flex;align-items:center;gap:6px;`;
    const nozGroupChips = ['ALL'].concat(groups.map((g) => g.code)).map((code) => { const on = S.nozGroup === code; return { label: code === 'ALL' ? 'ทุกกลุ่ม' : code, onClick: () => this.setState({ nozGroup: code }), style: nozChip(on) }; });
    const nozStatusDefs = [['ALL', 'ทุกสถานะ', 'var(--neutral-400)'], ['flowing', 'กำลังส่งน้ำ', '#1F8A56'], ['active', 'พร้อมใช้งาน', '#74BD96'], ['maintenance', 'ซ่อมบำรุง', '#C8901F'], ['inactive', 'ไม่ใช้งาน', '#A1A9A4']];
    const nozStatusChips = nozStatusDefs.map((d) => { const on = S.nozStatus === d[0]; return { label: d[1], dot: d[2], onClick: () => this.setState({ nozStatus: d[0] }), style: nozChip(on) }; });
    const nozStColor = { flowing: '#1F8A56', active: '#74BD96', maintenance: '#C8901F', inactive: '#A1A9A4' };
    const nozStLabel = { flowing: 'ส่งน้ำ', active: 'พร้อม', maintenance: 'ซ่อม', inactive: 'ปิด' };
    const nozzlesFiltered = DS.nozzles.filter((n) => (S.nozGroup === 'ALL' || n.group === S.nozGroup) && (S.nozStatus === 'ALL' || n.status === S.nozStatus));
    const cards = nozzlesFiltered.slice(0, 60).map((n) => ({
      id: n.id, owner: n.owner, area: n.area, dot: nozStColor[n.status] || '#A1A9A4', stLabel: nozStLabel[n.status] || n.status,
      bg: n.status === 'flowing' ? 'var(--green-50)' : n.status === 'inactive' ? 'var(--surface-sunken)' : 'var(--surface-card)',
      border: n.status === 'flowing' ? 'var(--green-200)' : n.status === 'maintenance' ? 'var(--amber-200)' : 'var(--border-subtle)',
      rawGroup: n.group, rawGroupColor: n.groupColor, rawStatus: n.status,
    }));

    // ---- reports ----
    const reports = [
      { icon: 'file', name: 'รายงานประจำเดือน', desc: 'สรุปปริมาณน้ำ พื้นที่ เกษตรกร ตาม KPI รายเดือน', format: 'PDF', iconBg: 'var(--red-50)', iconColor: 'var(--red-500)' },
      { icon: 'sprout', name: 'รายงานเกษตรกร', desc: 'ทะเบียนเกษตรกรพร้อมพื้นที่และสถานะการใช้น้ำ', format: 'Excel', iconBg: 'var(--green-50)', iconColor: 'var(--green-600)' },
      { icon: 'droplet', name: 'รายงานการใช้น้ำ', desc: 'ปริมาณส่งแต่ละหัวจ่าย เปรียบเทียบแผน vs จริง', format: 'Excel', iconBg: 'var(--blue-50)', iconColor: 'var(--blue-500)' },
      { icon: 'map', name: 'รายงาน GIS', desc: 'ส่งออกพิกัด KMZ/KML พร้อมข้อมูลสถานะหัวจ่าย', format: 'KMZ', iconBg: 'var(--amber-50)', iconColor: 'var(--amber-status-600)' },
      { icon: 'wrench', name: 'รายงานซ่อมบำรุง', desc: 'ประวัติและแผนซ่อมหัวจ่ายทั้งระบบ', format: 'PDF', iconBg: 'var(--surface-sunken)', iconColor: 'var(--neutral-600)' },
      { icon: 'grid', name: 'Executive Dashboard', desc: 'สรุปภาพรวมสำหรับผู้บริหารระดับสูง', format: 'PDF', iconBg: 'var(--green-50)', iconColor: 'var(--co-uba)' },
    ].map((r) => ({ ...r, icon: ic(r.icon, 20, r.iconColor), onClick: () => this.showToast('กำลังสร้าง ' + r.name + '…') }));
    const quarters = [['Q1', 2400, 2280], ['Q2', 2600, 2510], ['Q3', 2700, 2520], ['Q4', 2800, 2803]];
    const pmax = 3000, pBaseY = 170, pH = 140, pslot = 560 / 4;
    let planBars = [];
    quarters.forEach((q2, i) => {
      const bw = pslot * 0.26;
      [['plan', q2[1], 'var(--green-200)', -1], ['act', q2[2], 'var(--green-600)', 0]].forEach((d, j) => {
        const h = (d[1] / pmax) * pH; const x = i * pslot + pslot / 2 + (j - 0.5) * bw * 1.15 - bw / 2 + bw * 0.075;
        planBars.push({ x: x.toFixed(1), y: (pBaseY - h).toFixed(1), w: bw.toFixed(1), h: h.toFixed(1), cx: (i * pslot + pslot / 2).toFixed(1), label: q2[0], color: d[2] });
      });
    });
    const planGrid = [0, 1000, 2000, 3000].map((v) => ({ y: (pBaseY - (v / pmax) * pH).toFixed(1), ty: (pBaseY - (v / pmax) * pH - 3).toFixed(1), label: (v / 1000) + 'k' }));

    // ---- alerts ----
    const toneMap = { warning: { a: 'var(--amber-status-500)', bg: 'var(--status-warning-bg)' }, info: { a: 'var(--blue-500)', bg: 'var(--status-info-bg)' }, danger: { a: 'var(--red-500)', bg: 'var(--status-danger-bg)' }, success: { a: 'var(--green-600)', bg: 'var(--status-success-bg)' } };
    const alerts = DS.alerts.map((d) => { const t = toneMap[d.tone] || toneMap.info; return { title: d.title, body: d.body, tag: d.tag, time: d.time, accent: t.a, iconBg: t.bg, tagBg: t.bg, icon: ic(d.icon, 18, t.a) }; });

    // ---- users ----
    const userTab = (id, label) => { const on = S.userTab === id; return { id, label, onClick: () => this.setState({ userTab: id }), style: `padding:9px 18px;border:none;border-bottom:2px solid ${on ? 'var(--green-600)' : 'transparent'};background:none;font-family:inherit;font-size:13px;font-weight:${on ? '600' : '500'};cursor:pointer;color:${on ? 'var(--green-700)' : 'var(--text-muted)'};` }; };
    const userTabs = [userTab('allUsers', 'ผู้ใช้ทั้งหมด'), userTab('roles', 'สิทธิ์การใช้งาน')];
    const roleChip = { admin: { bg: 'var(--green-50)', fg: 'var(--green-700)', label: 'Administrator' }, operator: { bg: 'var(--blue-50)', fg: 'var(--blue-600)', label: 'Operator' }, viewer: { bg: 'var(--surface-sunken)', fg: 'var(--neutral-600)', label: 'Viewer' } };
    const users = DS.users.map((u) => { const rc = roleChip[u.role] || roleChip.viewer; return { name: u.name, email: u.email, groups: u.groups, avBg: u.av, initial: u.initial, roleBg: rc.bg, roleFg: rc.fg, roleLabel: rc.label, status: u.active ? 'ใช้งาน' : 'พักงาน', stBg: u.active ? 'var(--status-success-bg)' : 'var(--surface-sunken)', stFg: u.active ? 'var(--status-success-fg)' : 'var(--neutral-600)', rawId: u.rawId, username: u.username, role: u.role, rawGroupIds: u.rawGroupIds, rawActive: u.rawActive }; });
    const mkPerms = (arr) => arr.map((p) => ({ label: p[1], icon: ic(p[0] ? 'check' : 'x', 15, p[0] ? 'var(--green-600)' : 'var(--red-500)'), color: p[0] ? 'var(--green-600)' : 'var(--red-500)' }));
    const rolePerms = [
      { title: 'Administrator', subtitle: 'สิทธิ์เต็มระบบ', icon: ic('shield', 17, 'var(--green-700)'), iconBg: 'var(--green-50)', iconColor: 'var(--green-700)', perms: mkPerms([[1, 'ดูและแก้ไขข้อมูลทั้งหมด'], [1, 'เพิ่ม/ลบ ผู้ใช้งาน'], [1, 'อนุมัติเพิ่มพื้นที่/แนวส่งน้ำ'], [1, 'กำหนดรอบน้ำ'], [1, 'Export รายงานทุกประเภท'], [1, 'ตั้งค่าระบบ & Integration']]) },
      { title: 'Operator', subtitle: 'ปฏิบัติงานประจำวัน', icon: ic('sliders', 17, 'var(--blue-600)'), iconBg: 'var(--blue-50)', iconColor: 'var(--blue-600)', perms: mkPerms([[1, 'ดูข้อมูลทั้งหมด'], [1, 'แก้ไขข้อมูลเกษตรกร/หัวจ่าย'], [1, 'บันทึกการส่งน้ำแต่ละรอบ'], [1, 'แจ้งซ่อมบำรุง & Export'], [0, 'จัดการผู้ใช้'], [0, 'ตั้งค่าระบบ']]) },
      { title: 'Viewer', subtitle: 'ดูอย่างเดียว', icon: ic('eye', 17, 'var(--neutral-600)'), iconBg: 'var(--surface-sunken)', iconColor: 'var(--neutral-600)', perms: mkPerms([[1, 'ดู Dashboard & แผนที่'], [1, 'ดูรายงาน'], [0, 'แก้ไขข้อมูลใดๆ'], [0, 'เพิ่มพื้นที่'], [0, 'จัดการผู้ใช้'], [0, 'ตั้งค่าระบบ']]) },
    ];

    // ---- settings ----
    const F = (label, labelEn, value, mono, span) => ({ label, labelEn, value, mono: mono ? 'var(--font-mono)' : 'inherit', span: span ? 'grid-column:1/-1;' : '' });
    const settingPanels = [
      { title: 'ข้อมูลโรงงาน', icon: ic('factory', 18), fields: [F('ชื่อโรงงาน', 'Plant', 'อุบล ไบโอ เอทานอล (มหาชน)', 0, 1), F('กำลังผลิต Vinasse', 'm³/ปี', '180,000', 1), F('แรงดันระบบ', 'bar', '3.2', 1), F('pH เป้าหมาย', 'target', '6.8 – 7.2', 1), F('จำนวนกลุ่ม', 'groups', String(groups.length), 1)] },
      { title: 'การแจ้งเตือน', icon: ic('bell', 18), fields: [F('แรงดันต่ำสุด', 'bar', '1.5', 1), F('แรงดันสูงสุด', 'bar', '5.0', 1), F('Line OA', 'channel', 'เปิดใช้งาน', 0), F('อีเมล Admin', 'email', 'admin@ubonbioethanol.com', 1, 1)] },
      { title: 'Database & Integration', icon: ic('settings', 18), fields: [F('SAP S/4HANA', 'OData', 'https://sap.ube.co.th:8443', 1, 1), F('GIS API', 'DOAE', 'https://gis.doae.go.th/api', 1, 1), F('MQTT Sensors', 'broker', 'mqtt://sensors.ube.co.th', 1, 1)] },
      { title: 'Dashboard', icon: ic('sliders', 18), fields: [F('รีเฟรช', 'วินาที', '30', 1), F('ย้อนหลัง', 'เดือน', '12', 1), F('ภาษา', 'language', 'ไทย / English', 0), F('Theme', 'theme', 'Light (UBE)', 0)] },
    ];

    const clockIcon = ic('clock', 14), warnIcon = ic('warn', 17), arrowIcon = ic('arrow', 16);
    const filterIcon = ic('filter', 14), searchIcon = ic('search', 15), downloadIcon = ic('download', 13), checkIcon = ic('check', 15, '#fff'), lockIcon = ic('lock', 40), logoutIcon = ic('logout', 15);

    const denied = (
      <div style={s('text-align:center;padding:70px 20px;background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);')}>
        <span style={s('display:inline-flex;color:var(--text-faint);margin-bottom:12px;')}>{lockIcon}</span>
        <h2 style={s('font-size:17px;color:var(--text-strong);margin-bottom:6px;')}>ไม่มีสิทธิ์เข้าถึง</h2>
        <p style={s('font-size:13px;color:var(--text-muted);')}>หน้านี้สำหรับ Administrator เท่านั้น · This page is restricted to administrators</p>
      </div>
    );

    const loadingOverlay = (live && S.loading) ? (
      <div style={s('position:fixed;inset:0;z-index:1300;display:flex;align-items:center;justify-content:center;background:rgba(247,248,247,0.7);font-size:13px;color:var(--text-muted);')}>กำลังโหลดข้อมูล…</div>
    ) : null;
    const loadErrBar = (live && S.loadErr) ? (
      <div style={s('display:flex;align-items:center;gap:10px;padding:10px 15px;background:var(--status-danger-bg);border:1px solid var(--red-500);border-radius:var(--radius-md);font-size:12.5px;color:var(--red-600);margin-bottom:14px;')}>
        <span>เชื่อมต่อ API ไม่สำเร็จ: {S.loadErr}</span>
        <button onClick={() => this.loadData()} style={s('margin-left:auto;padding:4px 10px;border:1px solid var(--red-500);border-radius:var(--radius-sm);background:#fff;color:var(--red-600);font-family:inherit;font-size:11.5px;cursor:pointer;')}>ลองใหม่</button>
      </div>
    ) : null;

    return (
      <div id="root-scroll" style={s('display:flex;flex-direction:column;height:100vh;font-family:var(--font-sans);color:var(--text-body);background:var(--surface-app);overflow:hidden;')}>

        {/* ============ TOPBAR ============ */}
        <header style={s('height:56px;flex:0 0 56px;background:var(--surface-card);border-bottom:1px solid var(--border-subtle);display:flex;align-items:center;gap:16px;padding:0 18px;z-index:100;')}>
          <div style={s('display:flex;align-items:center;gap:12px;')}>
            <img src="assets/ube-logo.png" alt="UBE Ubon Bio Ethanol Group" style={s('height:38px;width:auto;object-fit:contain;display:block;')} />
            <div style={s('width:1px;height:30px;background:var(--border-subtle);flex-shrink:0;')}></div>
            <div style={s('line-height:1.2;')}>
              <div style={s('font-size:13.5px;font-weight:700;color:var(--text-strong);')}>Venus WMS</div>
              <div style={s('font-size:10px;color:var(--text-muted);')}>ระบบบริหารจัดการน้ำ Vinasse</div>
            </div>
          </div>

          <div style={s('display:flex;align-items:center;gap:7px;margin-left:18px;padding-left:18px;border-left:1px solid var(--border-subtle);')}>
            <span className="vn-flowdot" style={s('width:8px;height:8px;border-radius:50%;background:var(--co-usa);display:inline-block;')}></span>
            <span style={s('font-size:11.5px;color:var(--text-muted);')}>รอบ <b style={s('color:var(--text-strong);')}>{K.round}</b> · กำลังส่งน้ำ</span>
          </div>

          <div style={s('margin-left:auto;display:flex;align-items:center;gap:14px;')}>
            <div style={s('display:flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:12px;color:var(--text-muted);')}>{clockIcon}<span>{S.time}</span></div>

            {!live ? (
              <div style={s('display:flex;background:var(--surface-sunken);border:1px solid var(--border-subtle);border-radius:var(--radius-pill);padding:3px;gap:2px;')}>
                {roleTabs.map((rt) => (<button key={rt.id} onClick={rt.onClick} style={s(rt.style)}>{rt.icon}<span>{rt.label}</span></button>))}
              </div>
            ) : null}

            <div style={s('display:flex;align-items:center;gap:9px;padding-left:14px;border-left:1px solid var(--border-subtle);')}>
              <div style={s('width:30px;height:30px;border-radius:50%;background:' + roleMeta.av + ';color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;')}>{roleMeta.initial}</div>
              <div style={s('line-height:1.2;')}>
                <div style={s('font-size:12.5px;font-weight:600;color:var(--text-strong);')}>{roleMeta.name}</div>
                <div style={s('font-size:10px;color:var(--text-muted);')}>{roleMeta.en}</div>
              </div>
              {live ? (
                <button onClick={() => this.logout()} title="ออกจากระบบ" style={s('display:flex;align-items:center;justify-content:center;width:30px;height:30px;margin-left:4px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface-card);color:var(--text-muted);cursor:pointer;')}>{logoutIcon}</button>
              ) : null}
            </div>
          </div>
        </header>

        {/* ============ BODY ============ */}
        <div style={s('display:flex;flex:1;min-height:0;')}>

          {/* ===== SIDEBAR ===== */}
          <aside style={s('width:248px;flex:0 0 248px;background:var(--surface-card);border-right:1px solid var(--border-subtle);display:flex;flex-direction:column;overflow-y:auto;')}>
            <nav style={s('flex:1;padding:12px 12px 4px;')}>
              {navSections.map((sec, si) => (
                <div key={si} style={s('margin-bottom:14px;')}>
                  <div style={s('font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text-faint);padding:6px 10px 6px;')}>{sec.label}</div>
                  {sec.items.map((it) => (
                    <button key={it.id} className="vn-nav" onClick={it.onClick} style={s(it.style)}>
                      <span style={s('display:flex;width:20px;color:' + it.iconColor + ';')}>{it.icon}</span>
                      <span style={s('flex:1;text-align:left;line-height:1.15;')}>{it.label}<span style={s('display:block;font-size:9.5px;font-weight:400;color:var(--text-faint);letter-spacing:.02em;')}>{it.labelEn}</span></span>
                      {it.badge ? <span style={s(it.badgeStyle)}>{it.badge}</span> : null}
                    </button>
                  ))}
                </div>
              ))}
            </nav>

            {/* live flow widget */}
            <div style={s('margin:4px 16px 16px;padding:13px 14px;background:var(--green-50);border:1px solid var(--green-100);border-radius:var(--radius-lg);')}>
              <div style={s('display:flex;align-items:center;justify-content:space-between;margin-bottom:9px;')}>
                <span style={s('font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--green-700);')}>การไหลขณะนี้</span>
                <span className="vn-flowdot" style={s('width:7px;height:7px;border-radius:50%;background:var(--green-500);')}></span>
              </div>
              <div style={s('height:7px;border-radius:4px;background:var(--green-100);overflow:hidden;margin-bottom:9px;')}>
                <div className="vn-flowfill" style={s('height:100%;width:72%;border-radius:4px;background:linear-gradient(90deg,var(--co-usa),var(--amber-400));')}></div>
              </div>
              <div style={s('display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted);margin-bottom:3px;')}><span>ปริมาณ</span><b style={s('font-family:var(--font-mono);color:var(--text-strong);')}>864 m³/hr</b></div>
              <div style={s('display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted);')}><span>แรงดัน</span><b style={s('font-family:var(--font-mono);color:var(--text-strong);')}>3.2 bar</b></div>
            </div>
          </aside>

          {/* ===== MAIN ===== */}
          <main style={s('flex:1;min-width:0;overflow-y:auto;background:var(--surface-app);')}>
            <div style={s('padding:22px 26px 40px;max-width:1280px;')}>

              {loadErrBar}

              {/* page heading */}
              <div style={s('display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:18px;gap:16px;flex-wrap:wrap;')}>
                <div>
                  <div className="ube-eyebrow" style={s('margin-bottom:5px;')}>{head[0]}</div>
                  <h1 style={s('font-size:var(--text-2xl);font-weight:600;color:var(--text-strong);line-height:1.15;')}>{head[1]}</h1>
                  <div style={s('font-size:13px;color:var(--text-muted);margin-top:3px;')}>{head[2]}</div>
                </div>
                <div style={s('display:flex;gap:8px;')}>
                  {pageActions.map((a, i) => (<button key={i} onClick={a.onClick} style={s(a.style)}>{a.icon}<span>{a.label}</span></button>))}
                </div>
              </div>

              {/* ===================== DASHBOARD ===================== */}
              <div style={{ ...s('flex-direction:column;gap:18px;'), display: show.dashboard }}>
                <div style={s('display:flex;align-items:center;gap:11px;padding:11px 15px;background:var(--status-warning-bg);border:1px solid var(--amber-200);border-radius:var(--radius-md);font-size:12.5px;color:var(--amber-700);')}>
                  <span style={s('flex:0 0 auto;color:var(--amber-status-600);')}>{warnIcon}</span>
                  <span>หัวจ่าย <b style={s('font-family:var(--font-mono);')}>WM-NS-087</b> รายงานแรงดันต่ำ — รอการตรวจสอบ · หัวจ่าย <b style={s('font-family:var(--font-mono);')}>WM-KL-034</b> กำหนดซ่อมบำรุง 17 มิ.ย. 2568</span>
                </div>

                <div style={s('display:grid;grid-template-columns:repeat(5,1fr);gap:14px;')}>
                  {kpis.map((k, i) => (
                    <div key={i} className="vn-card-hover" onClick={() => this.setState({ kpiModal: i })} style={s('background:' + (S.kpiModal === i ? 'var(--green-50)' : 'var(--surface-card)') + ';border:' + (S.kpiModal === i ? '2px solid ' + k.accent : '1px solid var(--border-subtle)') + ';border-radius:var(--radius-lg);box-shadow:' + (S.kpiModal === i ? '0 0 0 3px ' + k.accent + '22,var(--shadow-md)' : 'var(--shadow-sm)') + ';padding:' + (S.kpiModal === i ? '14px 15px' : '15px 16px') + ';position:relative;overflow:hidden;cursor:pointer;')}>
                      <div style={s('position:absolute;top:0;left:0;right:0;height:3px;background:' + k.accent + ';')}></div>
                      <div style={s('display:flex;align-items:center;gap:7px;margin-bottom:11px;')}>
                        <span style={s('color:' + k.accent + ';')}>{k.icon}</span>
                        <span style={s('font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted);')}>{k.label}</span>
                      </div>
                      <div style={s('font-size:26px;font-weight:700;color:var(--text-strong);line-height:1;font-family:var(--font-mono);')}>{k.value}</div>
                      <div style={s('display:flex;align-items:center;justify-content:space-between;margin-top:7px;')}>
                        <span style={s('font-size:11px;color:var(--text-muted);')}>{k.unit}</span>
                        <span style={s('font-size:11px;font-weight:600;color:' + k.trendColor + ';')}>{k.trend}</span>
                      </div>
                    </div>
                  ))}
                </div>

                <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:18px 20px;')}>
                  <div style={s('display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;')}>
                    <h2 style={s('font-size:var(--text-lg);font-weight:600;color:var(--text-strong);')}>กระบวนการส่งน้ำ Vinasse <span style={s('font-weight:400;color:var(--text-muted);font-size:13px;')}>Real-time pipeline</span></h2>
                    <span style={s('font-size:11px;color:var(--green-600);display:flex;align-items:center;gap:6px;')}><span className="vn-flowdot" style={s('width:7px;height:7px;border-radius:50%;background:var(--green-500);')}></span>ระบบทำงานปกติ</span>
                  </div>
                  <div style={s('display:flex;align-items:stretch;gap:0;overflow-x:auto;padding-bottom:4px;')}>
                    {pipeline.map((p, i) => (
                      <div key={i} style={s('flex:1;min-width:138px;display:flex;align-items:center;')}>
                        <div style={s('flex:1;border:1px solid ' + p.border + ';background:' + p.bg + ';border-radius:var(--radius-md);padding:13px 12px;text-align:center;min-height:118px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;')}>
                          <span style={s('color:' + p.iconColor + ';')}>{p.icon}</span>
                          <div style={s('font-size:12.5px;font-weight:600;color:var(--text-strong);')}>{p.name}</div>
                          <div style={s('font-size:10px;color:var(--text-muted);line-height:1.3;')}>{p.sub}</div>
                          <div style={s('font-size:12px;font-weight:700;color:' + p.valColor + ';font-family:var(--font-mono);margin-top:1px;')}>{p.val}</div>
                        </div>
                        {p.arrow ? <span style={s('flex:0 0 22px;display:flex;justify-content:center;color:' + p.arrowColor + ';')}>{arrowIcon}</span> : null}
                      </div>
                    ))}
                  </div>
                </div>

                <div style={s('display:grid;grid-template-columns:1.5fr 1fr;gap:18px;')}>
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:18px 20px;')}>
                    <div style={s('display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;')}>
                      <h3 style={s('font-size:13px;font-weight:600;color:var(--text-strong);')}>ปริมาณน้ำส่งรายเดือน <span style={s('color:var(--text-muted);font-weight:400;')}>ลบ.ม.</span></h3>
                      <span style={s('font-size:11px;color:var(--text-muted);')}>ปีงบประมาณ 2568</span>
                    </div>
                    <svg viewBox="0 0 560 210" style={s('width:100%;height:auto;')}>
                      {gridLines.map((g, i) => (
                        <React.Fragment key={i}>
                          <line x1="0" y1={g.y} x2="560" y2={g.y} stroke="var(--border-subtle)" strokeWidth="1"></line>
                          <text x="0" y={g.ty} fontSize="9" fill="var(--text-faint)" fontFamily="var(--font-mono)">{g.label}</text>
                        </React.Fragment>
                      ))}
                      {monthlyBars.map((b, i) => (
                        <React.Fragment key={i}>
                          <rect x={b.x} y={b.y} width={b.w} height={b.h} rx="2.5" fill={b.color} style={{cursor:'pointer'}} onClick={() => this.setState({ barDetail: S.barDetail === i ? null : i, barDrillDown: { monthIdx: i, label: b.label, rawVal: b.rawVal, color: b.color } })}></rect>
                          {S.barDetail === i && <rect x={parseFloat(b.x)-2} y={parseFloat(b.y)-2} width={parseFloat(b.w)+4} height={parseFloat(b.h)+4} rx="3.5" fill="none" stroke={b.color} strokeWidth="2.5"></rect>}
                          <text x={b.cx} y={String(parseFloat(b.y) - 3)} fontSize="8" fill={b.color} textAnchor="middle" fontWeight="600">{(b.rawVal/1000).toFixed(1)}k</text>
                          <text x={b.cx} y="202" fontSize="9" fill={S.barDetail === i ? b.color : "var(--text-muted)"} textAnchor="middle" fontWeight={S.barDetail === i ? "700" : "400"}>{b.label}</text>
                        </React.Fragment>
                      ))}
                    </svg>
                    {S.barDetail !== null && monthlyBars[S.barDetail] && (() => {
                      const b = monthlyBars[S.barDetail]; const prev = S.barDetail > 0 ? monthlyBars[S.barDetail - 1] : null;
                      const diff = prev ? b.rawVal - prev.rawVal : 0; const pct = Math.round(b.rawVal / 52000 * 100);
                      const ytd = months.slice(0, S.barDetail + 1).reduce((acc, m) => acc + m[1], 0);
                      return (
                        <div style={s('margin-top:10px;background:var(--surface-sunken);border:1px solid var(--border-subtle);border-radius:var(--radius-md);padding:12px 16px;')}>
                          <div style={s('display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;')}>
                            <span style={s('font-size:13px;font-weight:700;color:var(--text-strong);')}>รายละเอียด {b.label}</span>
                            <button onClick={() => this.setState({ barDetail: null })} style={s('background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:16px;line-height:1;')}>X</button>
                          </div>
                          <div style={s('display:grid;grid-template-columns:repeat(4,1fr);gap:10px;')}>
                            {[
                              ['ปริมาณน้ำ', b.rawVal.toLocaleString() + ' ลบ.ม.', b.color],
                              ['เทียบเดือนก่อน', (prev ? (diff >= 0 ? '+' : '') + diff.toLocaleString() : 'N/A') + ' ลบ.ม.', diff >= 0 ? 'var(--green-600)' : 'var(--red-500)'],
                              ['% ของปีงบ', pct + '%', 'var(--text-strong)'],
                              ['สะสม YTD', (ytd / 1000).toFixed(1) + 'k ลบ.ม.', 'var(--text-strong)'],
                            ].map((r, ri) => (
                              <div key={ri} style={s('background:var(--surface-card);border-radius:var(--radius-md);padding:8px 10px;border:1px solid var(--border-subtle);')}>
                                <div style={s('font-size:10px;color:var(--text-muted);margin-bottom:3px;')}>{r[0]}</div>
                                <div style={s('font-size:13px;font-weight:700;color:' + r[2] + ';font-family:var(--font-mono);')}>{r[1]}</div>
                              </div>
                            ))}
                          </div>
                        </div>
                      );
                    })()}
                  </div>
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:18px 20px;')}>
                    <h3 style={s('font-size:13px;font-weight:600;color:var(--text-strong);margin-bottom:8px;')}>สถานะหัวจ่ายน้ำ <span style={s('color:var(--text-muted);font-weight:400;')}>{K.totalNozzles} หัว</span></h3>
                    <div style={s('display:flex;align-items:center;gap:16px;')}>
                      <svg viewBox="0 0 180 180" style={s('width:128px;height:128px;flex:0 0 auto;')}>
                        <g transform="rotate(-90 90 90)">
                          {donut.map((d, i) => (<circle key={i} cx="90" cy="90" r="56" fill="none" stroke={d.color} strokeWidth="22" strokeDasharray={d.dash} strokeDashoffset={d.offset}></circle>))}
                        </g>
                        <text x="90" y="84" textAnchor="middle" fontSize="26" fontWeight="700" fill="var(--text-strong)" fontFamily="var(--font-mono)">{K.totalNozzles}</text>
                        <text x="90" y="102" textAnchor="middle" fontSize="10" fill="var(--text-muted)">หัวจ่าย</text>
                      </svg>
                      <div style={s('flex:1;display:flex;flex-direction:column;gap:9px;')}>
                        {nozzleStatus.map((st, i) => (
                          <div key={i} style={s('display:flex;align-items:center;gap:8px;font-size:12px;')}>
                            <span style={s('width:9px;height:9px;border-radius:2px;background:' + st.color + ';')}></span>
                            <span style={s('flex:1;color:var(--text-body);')}>{st.label}</span>
                            <span style={s('font-size:10px;color:var(--text-muted);font-family:var(--font-mono);')}>{K.totalNozzles ? Math.round(st.v / K.totalNozzles * 100) : 0}%</span>
                            <b style={s('font-family:var(--font-mono);color:var(--text-strong);')}>{st.v}</b>
                          </div>
                        ))}
                      </div>
                    </div>
                  </div>
                </div>

                <div style={s('display:grid;grid-template-columns:1fr 1fr;gap:18px;')}>
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:18px 20px;')}>
                    <h3 style={s('font-size:13px;font-weight:600;color:var(--text-strong);margin-bottom:14px;')}>พื้นที่รับน้ำต่อกลุ่ม <span style={s('color:var(--text-muted);font-weight:400;')}>ไร่</span></h3>
                    <div style={s('display:flex;flex-direction:column;gap:10px;')}>
                      {areaBars.map((a, i) => (
                        <div key={i} style={s('display:flex;align-items:center;gap:10px;')}>
                          <span style={s('width:74px;flex:0 0 auto;font-size:11.5px;color:var(--text-body);')}>{a.code} · {a.th}</span>
                          <div style={s('flex:1;height:14px;background:var(--surface-sunken);border-radius:4px;overflow:hidden;')}>
                            <div style={s('height:100%;width:' + a.w + ';background:' + a.color + ';border-radius:4px;')}></div>
                          </div>
                          <span style={s('width:48px;text-align:right;font-size:11.5px;font-family:var(--font-mono);color:var(--text-strong);font-weight:600;')}>{a.rai}</span>
                        </div>
                      ))}
                    </div>
                  </div>
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:18px 20px;')}>
                    <h3 style={s('font-size:13px;font-weight:600;color:var(--text-strong);margin-bottom:14px;')}>พืชที่ปลูก <span style={s('color:var(--text-muted);font-weight:400;')}>แปลง</span></h3>
                    <div style={s('display:flex;flex-direction:column;gap:10px;')}>
                      {crops.map((c, i) => (
                        <div key={i} style={s('display:flex;align-items:center;gap:10px;')}>
                          <span style={s('width:96px;flex:0 0 auto;font-size:11.5px;color:var(--text-body);')}>{c.th}</span>
                          <div style={s('flex:1;height:14px;background:var(--surface-sunken);border-radius:4px;overflow:hidden;')}>
                            <div style={s('height:100%;width:' + c.w + ';background:' + c.color + ';border-radius:4px;')}></div>
                          </div>
                          <span style={s('width:40px;text-align:right;font-size:11.5px;font-family:var(--font-mono);color:var(--text-strong);font-weight:600;')}>{c.plots}</span>
                        </div>
                      ))}
                    </div>
                  </div>
                </div>
              </div>

              {/* ===================== GIS DASHBOARD ===================== */}
              <div style={{ ...s('flex-direction:column;gap:16px;'), display: show.gis }}>
                {/* KPI row */}
                <div style={s('display:grid;grid-template-columns:repeat(5,1fr);gap:14px;')}>
                  {GIS_KPIS.map((k, i) => (
                    <div key={i} className="vn-card-hover" style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:14px 15px;position:relative;overflow:hidden;')}>
                      <div style={s('position:absolute;top:0;left:0;right:0;height:3px;background:' + k.accent + ';')}></div>
                      <div style={s('display:flex;align-items:center;gap:9px;margin-bottom:11px;')}>
                        <span style={s('display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--radius-md);background:var(--surface-sunken);color:' + k.accent + ';flex:0 0 auto;')}>{ic(k.icon, 17, k.accent)}</span>
                        <span style={s('font-size:11px;font-weight:600;color:var(--text-muted);line-height:1.25;')}>{k.label}</span>
                      </div>
                      <div style={s('display:flex;align-items:baseline;gap:5px;')}>
                        <span style={s('font-size:23px;font-weight:700;color:var(--text-strong);font-family:var(--font-mono);line-height:1;')}>{k.value}</span>
                        <span style={s('font-size:11px;color:var(--text-muted);')}>{k.unit}</span>
                      </div>
                      <div style={s('font-size:10.5px;font-weight:600;color:' + k.trendColor + ';margin-top:7px;')}>{k.trend}</div>
                    </div>
                  ))}
                </div>

                {/* map + right column */}
                <div style={s('display:grid;grid-template-columns:1fr 340px;gap:16px;align-items:start;')}>
                  {/* map card */}
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden;')}>
                    <div style={s('display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border-subtle);')}>
                      <span style={s('font-size:13px;font-weight:700;color:var(--text-strong);')}>แผนที่แนวท่อโครงการและจุดแปลงเกษตรกร</span>
                      <span style={s('font-size:11px;color:var(--text-muted);font-family:var(--font-mono);')}>อ.เมือง · จ.อุบลราชธานี</span>
                    </div>
                    <div style={s('position:relative;')}>
                      <div id="gisMap" style={s('height:580px;width:100%;')}></div>
                      {/* legend overlay */}
                      <div style={s('position:absolute;top:12px;right:12px;z-index:500;width:206px;max-height:556px;overflow:auto;background:rgba(255,255,255,0.96);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:var(--shadow-md);padding:12px 13px;')}>
                        <div style={s('font-size:11.5px;font-weight:700;color:var(--text-strong);margin-bottom:9px;')}>คำอธิบายสัญลักษณ์</div>
                        {gisLegend.map((grp, gi) => (
                          <div key={gi} style={s('margin-bottom:9px;')}>
                            <div style={s('font-size:9.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--text-faint);margin-bottom:5px;')}>{grp.title}</div>
                            {grp.items.map((it, ii) => (
                              <div key={ii} style={s('display:flex;align-items:center;gap:8px;padding:2.5px 0;font-size:11px;color:var(--text-body);')}>
                                <span style={s('width:18px;flex:0 0 18px;display:flex;align-items:center;justify-content:center;')}>
                                  {it.kind === 'dot' ? <span style={s('width:11px;height:11px;border-radius:50%;border:1.5px solid #fff;box-shadow:0 0 0 1px ' + it.color + ';background:' + it.color + ';')}></span>
                                    : it.kind === 'box' ? <span style={s('width:13px;height:13px;border-radius:3px;background:' + it.color + ';border:1.5px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.1);')}></span>
                                      : <span style={s('width:18px;height:0;border-top:' + it.w + 'px ' + (it.dash ? 'dashed' : it.dot ? 'dotted' : 'solid') + ' ' + it.color + ';')}></span>}
                                </span>
                                <span style={s('line-height:1.2;')}>{it.label}</span>
                              </div>
                            ))}
                          </div>
                        ))}
                      </div>
                      {/* basemap toggle */}
                      <div style={s('position:absolute;bottom:14px;right:14px;z-index:500;display:flex;background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-pill);padding:3px;box-shadow:var(--shadow-md);')}>
                        {[['street', 'แผนที่'], ['satellite', 'ดาวเทียม'], ['hybrid', 'Hybrid']].map(([key, lbl]) => {
                          const on = S.gisBasemap === key;
                          return <button key={key} onClick={() => this.setGisBasemap(key)} style={s('padding:5px 13px;border:none;border-radius:var(--radius-pill);font-family:inherit;font-size:11.5px;font-weight:' + (on ? '600' : '500') + ';cursor:pointer;background:' + (on ? 'var(--color-primary)' : 'transparent') + ';color:' + (on ? '#fff' : 'var(--text-muted)') + ';')}>{lbl}</button>;
                        })}
                      </div>
                    </div>
                  </div>

                  {/* right column */}
                  <div style={s('display:flex;flex-direction:column;gap:16px;')}>
                    {/* area donut */}
                    <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:16px 18px;')}>
                      <div style={s('font-size:13px;font-weight:700;color:var(--text-strong);margin-bottom:12px;')}>ภาพรวมพื้นที่</div>
                      <div style={s('display:flex;align-items:center;gap:14px;')}>
                        <svg viewBox="0 0 160 160" style={s('width:118px;height:118px;flex:0 0 auto;')}>
                          <g transform="rotate(-90 80 80)">
                            {gisAreaDonut.map((d, i) => (<circle key={i} cx="80" cy="80" r="54" fill="none" stroke={d.color} strokeWidth="20" strokeDasharray={d.dash} strokeDashoffset={d.offset}></circle>))}
                          </g>
                          <text x="80" y="76" textAnchor="middle" fontSize="17" fontWeight="700" fill="var(--text-strong)" fontFamily="var(--font-mono)">18,642</text>
                          <text x="80" y="92" textAnchor="middle" fontSize="9.5" fill="var(--text-muted)">พื้นที่เพาะปลูก (ไร่)</text>
                        </svg>
                        <div style={s('flex:1;display:flex;flex-direction:column;gap:8px;')}>
                          {GIS_AREA.map((a, i) => (
                            <div key={i} style={s('display:flex;align-items:center;gap:7px;font-size:11px;')}>
                              <span style={s('width:9px;height:9px;border-radius:2px;background:' + a.color + ';flex:0 0 auto;')}></span>
                              <span style={s('flex:1;color:var(--text-body);')}>{a.label}</span>
                              <span style={s('font-family:var(--font-mono);color:var(--text-strong);font-weight:600;')}>{a.rai}</span>
                              <span style={s('color:var(--text-faint);width:46px;text-align:right;')}>{a.pct}</span>
                            </div>
                          ))}
                        </div>
                      </div>
                    </div>

                    {/* water status donut */}
                    <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:16px 18px;')}>
                      <div style={s('font-size:13px;font-weight:700;color:var(--text-strong);margin-bottom:12px;')}>สถานะการใช้น้ำ (แปลง)</div>
                      <div style={s('display:flex;align-items:center;gap:14px;')}>
                        <svg viewBox="0 0 160 160" style={s('width:118px;height:118px;flex:0 0 auto;')}>
                          <g transform="rotate(-90 80 80)">
                            {gisWaterDonut.map((d, i) => (<circle key={i} cx="80" cy="80" r="54" fill="none" stroke={d.color} strokeWidth="20" strokeDasharray={d.dash} strokeDashoffset={d.offset}></circle>))}
                          </g>
                          <text x="80" y="76" textAnchor="middle" fontSize="20" fontWeight="700" fill="var(--text-strong)" fontFamily="var(--font-mono)">2,563</text>
                          <text x="80" y="92" textAnchor="middle" fontSize="9.5" fill="var(--text-muted)">แปลง</text>
                        </svg>
                        <div style={s('flex:1;display:flex;flex-direction:column;gap:7px;')}>
                          {GIS_WATER.map((w, i) => (
                            <div key={i} style={s('display:flex;align-items:center;gap:7px;font-size:11px;')}>
                              <span style={s('width:9px;height:9px;border-radius:50%;background:' + w.color + ';flex:0 0 auto;')}></span>
                              <span style={s('flex:1;color:var(--text-body);')}>{w.label}</span>
                              <span style={s('font-family:var(--font-mono);color:var(--text-strong);font-weight:600;')}>{w.n}</span>
                              <span style={s('color:var(--text-faint);width:46px;text-align:right;')}>{w.pct}</span>
                            </div>
                          ))}
                        </div>
                      </div>
                    </div>

                    {/* top-5 tambon */}
                    <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden;')}>
                      <div style={s('font-size:13px;font-weight:700;color:var(--text-strong);padding:14px 18px 10px;')}>5 อันดับ พื้นที่เพาะปลูกมากที่สุด</div>
                      <table style={s('width:100%;border-collapse:collapse;font-size:11.5px;')}>
                        <thead><tr style={s('background:var(--surface-sunken);')}>
                          {['ลำดับ', 'ตำบล', 'พื้นที่ (ไร่)', 'แปลง'].map((h, i) => (<th key={i} style={s('padding:7px 14px;text-align:' + (i > 1 ? 'right' : 'left') + ';font-size:9.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--text-muted);')}>{h}</th>))}
                        </tr></thead>
                        <tbody>
                          {GIS_TOP_TAMBON.map((r, i) => (
                            <tr key={i} className="vn-row" style={s('border-top:1px solid var(--border-subtle);')}>
                              <td style={s('padding:8px 14px;color:var(--text-faint);font-family:var(--font-mono);')}>{r[0]}</td>
                              <td style={s('padding:8px 14px;color:var(--text-strong);font-weight:500;')}>{r[1]}</td>
                              <td style={s('padding:8px 14px;text-align:right;font-family:var(--font-mono);color:var(--text-body);')}>{r[2]}</td>
                              <td style={s('padding:8px 14px;text-align:right;font-family:var(--font-mono);color:var(--text-body);')}>{r[3]}</td>
                            </tr>
                          ))}
                        </tbody>
                      </table>
                      <button onClick={() => this.showToast('เปิดข้อมูลรายตำบลทั้งหมด')} style={s('width:100%;padding:10px;border:none;border-top:1px solid var(--border-subtle);background:var(--green-50);color:var(--green-700);font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;')}>ดูข้อมูลรายตำบลทั้งหมด →</button>
                    </div>
                  </div>
                </div>

                {/* bottom row */}
                <div style={s('display:grid;grid-template-columns:1.35fr 1fr 1fr;gap:16px;')}>
                  {/* crops summary */}
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:16px 18px;')}>
                    <div style={s('font-size:13px;font-weight:700;color:var(--text-strong);margin-bottom:14px;')}>สรุปตามชนิดพืช <span style={s('font-weight:400;color:var(--text-muted);font-size:11px;')}>พื้นที่: ไร่</span></div>
                    <div style={s('display:flex;gap:8px;')}>
                      {GIS_CROPS.map((c, i) => (
                        <div key={i} style={s('flex:1;text-align:center;')}>
                          <div style={s('display:flex;align-items:center;justify-content:center;width:38px;height:38px;margin:0 auto 8px;border-radius:50%;background:var(--green-50);color:var(--green-600);')}>{ic(c.icon, 19, 'var(--green-600)')}</div>
                          <div style={s('font-size:11px;color:var(--text-body);margin-bottom:3px;line-height:1.2;height:27px;')}>{c.th}</div>
                          <div style={s('font-size:13px;font-weight:700;font-family:var(--font-mono);color:var(--text-strong);')}>{c.rai}</div>
                          <div style={s('font-size:10.5px;font-weight:600;color:var(--green-600);')}>{c.pct}</div>
                        </div>
                      ))}
                    </div>
                  </div>

                  {/* monthly water bar chart */}
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:16px 18px;')}>
                    <div style={s('font-size:13px;font-weight:700;color:var(--text-strong);margin-bottom:8px;')}>ปริมาณการใช้น้ำรายเดือน <span style={s('font-weight:400;color:var(--text-muted);font-size:11px;')}>ล้าน ลบ.ม.</span></div>
                    <svg viewBox="0 0 430 165" style={s('width:100%;height:auto;')}>
                      {gisBars.map((b, i) => (
                        <React.Fragment key={i}>
                          <rect x={b.x} y={b.y} width={b.w} height={b.h} rx="3" fill={b.hi ? 'var(--co-usa)' : 'var(--green-300)'}></rect>
                          <text x={b.cx} y={b.ty} fontSize="9.5" fill={b.hi ? 'var(--co-usa)' : 'var(--text-muted)'} fontWeight={b.hi ? '700' : '400'} textAnchor="middle" fontFamily="var(--font-mono)">{b.val}</text>
                          <text x={b.cx} y="161" fontSize="9.5" fill="var(--text-muted)" textAnchor="middle">{b.label}</text>
                        </React.Fragment>
                      ))}
                    </svg>
                  </div>

                  {/* today usage + gauge */}
                  <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:16px 18px;')}>
                    <div style={s('font-size:13px;font-weight:700;color:var(--text-strong);margin-bottom:12px;')}>ข้อมูลการใช้น้ำวันนี้</div>
                    <div style={s('display:flex;align-items:center;gap:14px;')}>
                      <div style={s('flex:1;display:flex;flex-direction:column;gap:10px;')}>
                        {gisToday.map((t, i) => (
                          <div key={i}>
                            <div style={s('font-size:11px;color:var(--text-muted);margin-bottom:1px;')}>{t[0]}</div>
                            <div style={s('font-size:16px;font-weight:700;font-family:var(--font-mono);color:' + t[3] + ';')}>{t[1]} <span style={s('font-size:10px;font-weight:400;color:var(--text-faint);')}>{t[2]}</span></div>
                          </div>
                        ))}
                      </div>
                      <div style={s('flex:0 0 auto;text-align:center;')}>
                        <svg viewBox="0 0 120 120" style={s('width:96px;height:96px;')}>
                          <circle cx="60" cy="60" r="44" fill="none" stroke="var(--surface-sunken)" strokeWidth="13"></circle>
                          <g transform="rotate(-90 60 60)">
                            <circle cx="60" cy="60" r="44" fill="none" stroke="var(--blue-500)" strokeWidth="13" strokeLinecap="round" strokeDasharray={(0.4862 * 2 * Math.PI * 44).toFixed(1) + ' ' + (2 * Math.PI * 44).toFixed(1)}></circle>
                          </g>
                          <text x="60" y="58" textAnchor="middle" fontSize="20" fontWeight="700" fill="var(--text-strong)" fontFamily="var(--font-mono)">48.62%</text>
                          <text x="60" y="75" textAnchor="middle" fontSize="9.5" fill="var(--text-muted)">ของแผนจัดสรร</text>
                        </svg>
                        <div style={s('font-size:10.5px;color:var(--text-muted);margin-top:2px;')}>ร้อยละแผน</div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>

              {/* ===================== MAP ===================== */}
              <div style={{ ...s('flex-direction:column;gap:14px;'), display: show.map }}>
                {S.addMode ? (
                  <div style={s('display:flex;align-items:center;gap:11px;padding:11px 15px;background:var(--status-warning-bg);border:1px solid var(--amber-status-600);border-radius:var(--radius-md);font-size:12.5px;color:var(--amber-status-600);')}>
                    <span style={s('flex:0 0 auto;')}>{ic('plus', 16, 'var(--amber-status-600)')}</span>
                    <span><b>โหมดเพิ่มพื้นที่:</b> คลิกตำแหน่งบนแผนที่เพื่อปักหมุดแปลงใหม่ แล้วกรอกข้อมูล</span>
                    <button onClick={() => this.toggleAddMode()} style={s('margin-left:auto;padding:5px 12px;border:1px solid var(--amber-status-600);border-radius:var(--radius-sm);background:#fff;color:var(--amber-status-600);font-family:inherit;font-size:11.5px;cursor:pointer;')}>ยกเลิก</button>
                  </div>
                ) : null}
                <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden;')}>
                  <div style={s('display:flex;align-items:center;gap:8px;padding:11px 15px;border-bottom:1px solid var(--border-subtle);flex-wrap:wrap;')}>
                    <span style={s('font-size:11px;font-weight:600;color:var(--text-muted);display:flex;align-items:center;gap:5px;')}>{filterIcon}กรองกลุ่ม</span>
                    {mapChips.map((c, i) => (<button key={i} className="vn-chip" onClick={c.onClick} style={s(c.style)}><span style={s('width:8px;height:8px;border-radius:50%;background:' + c.dot + ';display:inline-block;')}></span>{c.label}</button>))}
                    <span style={s('margin-left:auto;font-size:11px;color:var(--text-muted);font-family:var(--font-mono);')}>15.165°N, 105.035°E · อ.นาเยีย / สว่างวีระวงศ์</span>
                  </div>
                  <div style={s('display:flex;min-height:0;')}>
                    <div id="vinasseMap" style={s('flex:1;height:540px;')}></div>
                    <div style={s('width:268px;flex:0 0 268px;border-left:1px solid var(--border-subtle);overflow-y:auto;height:540px;')}>
                      <div style={s('padding:13px 15px;border-bottom:1px solid var(--border-subtle);')}>
                        <div style={s('font-size:12px;font-weight:700;color:var(--text-strong);')}>กลุ่มเกษตรกร {groups.length} กลุ่ม</div>
                        <div style={s('font-size:11px;color:var(--text-muted);')}>{K.totalNozzles} หัวจ่าย · {totalRaiAll} ไร่</div>
                      </div>
                      {groupList.map((g, i) => (
                        <div key={i} className="vn-row" style={s('padding:11px 15px;border-bottom:1px solid var(--border-subtle);cursor:pointer;display:flex;align-items:center;gap:11px;')} onClick={g.onClick}>
                          <span style={s('width:11px;height:11px;border-radius:50%;background:' + g.color + ';flex:0 0 auto;')}></span>
                          <div style={s('flex:1;min-width:0;')}>
                            <div style={s('font-size:12.5px;font-weight:600;color:var(--text-strong);')}>กลุ่ม {g.no} · {g.code} <span style={s('font-weight:400;color:var(--text-muted);')}>{g.th}</span></div>
                            <div style={s('font-size:10.5px;color:var(--text-muted);')}>{g.company} · {g.nozzles} หัวจ่าย · {g.rai} ไร่</div>
                          </div>
                        </div>
                      ))}
                      <div style={s('padding:13px 15px;font-size:11px;color:var(--text-muted);line-height:1.6;')}>
                        <div style={s('display:flex;align-items:center;gap:7px;margin-bottom:5px;')}><span style={s('width:11px;height:11px;border-radius:50%;border:2px solid var(--green-700);background:#C8901F;flex:0 0 auto;')}></span> โรงงาน UBE (ต้นทางน้ำ)</div>
                        <div style={s('display:flex;align-items:center;gap:7px;margin-bottom:5px;')}><span style={s('width:9px;height:9px;border-radius:50%;border:2px solid var(--green-600);background:#fff;flex:0 0 auto;')}></span> สถานีสูบ / Buffer Tank</div>
                        <div style={s('display:flex;align-items:center;gap:7px;margin-bottom:5px;')}><span style={s('width:16px;height:3px;background:#0F5739;border-radius:2px;flex:0 0 auto;')}></span> แนวท่อส่งน้ำหลัก / ท่อแยกกลุ่ม</div>
                        <div style={s('display:flex;align-items:center;gap:7px;margin-bottom:6px;')}><span style={s('width:11px;height:11px;border:1px solid var(--green-600);background:rgba(31,138,86,0.35);flex:0 0 auto;')}></span> footprint พื้นที่รายแปลง (ขนาดตามไร่)</div>
                        คลิกแต่ละแปลงเพื่อดูรายละเอียด · กดปุ่ม “เพิ่มพื้นที่” แล้วคลิกแผนที่เพื่อเพิ่มแปลงใหม่
                      </div>
                    </div>
                  </div>
                </div>
              </div>

              {/* ===================== FARMERS ===================== */}
              <div style={{ ...s('flex-direction:column;gap:14px;'), display: show.farmers }}>
                <div style={s('display:flex;gap:12px;')}>
                  {farmerStats.map((st, i) => (
                    <div key={i} style={s('flex:1;background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:12px 15px;')}>
                      <div style={s('font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted);')}>{st.label}</div>
                      <div style={s('font-size:21px;font-weight:700;font-family:var(--font-mono);color:' + st.color + ';margin-top:4px;')}>{st.value}</div>
                    </div>
                  ))}
                </div>
                <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden;')}>
                  <div style={s('display:flex;align-items:center;justify-content:space-between;padding:13px 16px;border-bottom:1px solid var(--border-subtle);')}>
                    <div style={s('font-size:13px;font-weight:700;color:var(--text-strong);')}>ทะเบียนเกษตรกร <span style={s('font-weight:400;color:var(--text-muted);')}>{K.totalFarmers} ราย · แสดง {farmers.length}</span></div>
                    <div style={s('position:relative;display:flex;align-items:center;')}>
                      <span style={s('position:absolute;left:10px;color:var(--text-faint);display:flex;')}>{searchIcon}</span>
                      <input placeholder="ค้นหาชื่อ / รหัสหัวจ่าย / หมู่บ้าน" value={S.search} onChange={(e) => this.setState({ search: e.target.value })} style={s('width:280px;padding:8px 12px 8px 32px;font-family:inherit;font-size:12.5px;color:var(--text-strong);background:var(--surface-card);border:1px solid var(--border-field);border-radius:var(--radius-md);outline:none;')} />
                    </div>
                  </div>
                  <div style={s('overflow-x:auto;')}>
                    <table style={s('width:100%;border-collapse:collapse;font-size:12.5px;')}>
                      <thead>
                        <tr style={s('background:var(--surface-sunken);')}>
                          {farmerCols.map((c, i) => (<th key={i} style={s(c.style)}>{c.label}</th>))}
                        </tr>
                      </thead>
                      <tbody>
                        {farmers.slice(0, 200).map((f, i) => (
                          <tr key={i} className="vn-row" onClick={() => this.setState({ farmerDetail: f })} style={s('border-top:1px solid var(--border-subtle);cursor:pointer;')}>
                            <td style={s('padding:9px 14px;color:var(--text-faint);font-family:var(--font-mono);')}>{f.no}</td>
                            <td style={s('padding:9px 14px;color:var(--text-strong);font-weight:500;')}>{f.name}</td>
                            <td style={s('padding:9px 14px;')}><span style={s('display:inline-flex;align-items:center;gap:5px;font-family:var(--font-mono);font-size:11px;font-weight:600;color:#fff;background:' + f.groupColor + ';padding:2px 8px;border-radius:var(--radius-pill);')}>{f.group}</span></td>
                            <td style={s('padding:9px 14px;color:var(--text-body);')}>{f.village}</td>
                            <td style={s('padding:9px 14px;font-family:var(--font-mono);color:var(--text-body);')}>{f.nozzle}</td>
                            <td style={s('padding:9px 14px;font-family:var(--font-mono);color:var(--text-body);text-align:right;')}>{f.rai}</td>
                            <td style={s('padding:9px 14px;color:var(--text-body);')}>{f.crop}</td>
                            <td style={s('padding:9px 14px;')}><span style={s('display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:2px 9px;border-radius:var(--radius-pill);background:' + f.stBg + ';color:' + f.stFg + ';')}><span style={s('width:5px;height:5px;border-radius:50%;background:' + f.stFg + ';')}></span>{f.stLabel}</span></td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                </div>
              </div>

              {/* ===================== NOZZLES ===================== */}
              <div style={{ ...s('flex-direction:column;gap:14px;'), display: show.nozzles }}>
                <div style={s('display:flex;gap:12px;')}>
                  {nozzleStatTiles.map((st, i) => (
                    <div key={i} style={s('flex:1;background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:12px 15px;position:relative;overflow:hidden;')}>
                      <div style={s('position:absolute;top:0;left:0;bottom:0;width:3px;background:' + st.color + ';')}></div>
                      <div style={s('font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted);padding-left:6px;')}>{st.label}</div>
                      <div style={s('font-size:21px;font-weight:700;font-family:var(--font-mono);color:var(--text-strong);margin-top:4px;padding-left:6px;')}>{st.value}</div>
                    </div>
                  ))}
                </div>
                <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:16px;')}>
                  <div style={s('display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap;')}>
                    <span style={s('font-size:11px;font-weight:600;color:var(--text-muted);')}>กลุ่ม:</span>
                    {nozGroupChips.map((c, i) => (<button key={i} onClick={c.onClick} style={s(c.style)}>{c.label}</button>))}
                    <span style={s('margin-left:14px;font-size:11px;font-weight:600;color:var(--text-muted);')}>สถานะ:</span>
                    {nozStatusChips.map((c, i) => (<button key={i} onClick={c.onClick} style={s(c.style)}><span style={s('width:7px;height:7px;border-radius:50%;background:' + c.dot + ';display:inline-block;')}></span>{c.label}</button>))}
                    <span style={s('margin-left:auto;font-size:11px;color:var(--text-muted);')}>แสดง {cards.length} หัว</span>
                  </div>
                  <div style={s('display:grid;grid-template-columns:repeat(auto-fill,minmax(146px,1fr));gap:9px;')}>
                    {cards.map((n, i) => (
                      <div key={i} className="vn-card-hover" onClick={() => this.setState({ nozzleDetail: n })} style={s('border:1px solid ' + n.border + ';background:' + n.bg + ';border-radius:var(--radius-md);padding:10px 11px;cursor:pointer;')}>
                        <div style={s('display:flex;align-items:center;justify-content:space-between;margin-bottom:5px;')}>
                          <span style={s('font-family:var(--font-mono);font-size:11.5px;font-weight:700;color:var(--text-strong);')}>{n.id}</span>
                          <span style={s('width:8px;height:8px;border-radius:50%;background:' + n.dot + ';')}></span>
                        </div>
                        <div style={s('font-size:11px;color:var(--text-body);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;')}>{n.owner}</div>
                        <div style={s('display:flex;align-items:center;justify-content:space-between;margin-top:4px;')}>
                          <span style={s('font-size:10px;color:var(--text-muted);')}>{n.area} ไร่</span>
                          <span style={s('font-size:10px;font-weight:600;color:' + n.dot + ';')}>{n.stLabel}</span>
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              </div>

              {/* ===================== REPORTS ===================== */}
              <div style={{ ...s('flex-direction:column;gap:18px;'), display: show.reports }}>
                <div style={s('display:grid;grid-template-columns:repeat(3,1fr);gap:14px;')}>
                  {reports.map((r, i) => (
                    <div key={i} className="vn-card-hover" onClick={r.onClick} style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:17px;cursor:pointer;')}>
                      <div style={s('display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:var(--radius-md);background:' + r.iconBg + ';color:' + r.iconColor + ';margin-bottom:12px;')}>{r.icon}</div>
                      <div style={s('font-size:13.5px;font-weight:600;color:var(--text-strong);margin-bottom:4px;')}>{r.name}</div>
                      <div style={s('font-size:11.5px;color:var(--text-muted);line-height:1.45;margin-bottom:10px;')}>{r.desc}</div>
                      <div style={s('display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--green-600);')}>{r.format} {downloadIcon}</div>
                    </div>
                  ))}
                </div>
                <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);padding:18px 20px;')}>
                  <h3 style={s('font-size:13px;font-weight:600;color:var(--text-strong);margin-bottom:6px;')}>แผน vs จริง — พื้นที่รับน้ำรายไตรมาส <span style={s('color:var(--text-muted);font-weight:400;')}>ไร่</span></h3>
                  <svg viewBox="0 0 560 200" style={s('width:100%;height:auto;')}>
                    {planGrid.map((g, i) => (
                      <React.Fragment key={i}>
                        <line x1="0" y1={g.y} x2="560" y2={g.y} stroke="var(--border-subtle)" strokeWidth="1"></line>
                        <text x="0" y={g.ty} fontSize="9" fill="var(--text-faint)" fontFamily="var(--font-mono)">{g.label}</text>
                      </React.Fragment>
                    ))}
                    {planBars.map((b, i) => (
                      <React.Fragment key={i}>
                        <rect x={b.x} y={b.y} width={b.w} height={b.h} rx="2.5" fill={b.color}></rect>
                        <text x={parseFloat(b.x)+parseFloat(b.w)/2} y={String(parseFloat(b.y)-3)} fontSize="7.5" fill={b.color} textAnchor="middle" fontWeight="600">{parseFloat(b.h)>10 ? (parseFloat(b.h)/140*3000/1000).toFixed(1)+"k" : ""}</text>
                        <text x={b.cx} y="194" fontSize="9" fill="var(--text-muted)" textAnchor="middle">{b.label}</text>
                      </React.Fragment>
                    ))}
                  </svg>
                  <div style={s('display:flex;gap:18px;margin-top:8px;font-size:11.5px;color:var(--text-muted);')}>
                    <span style={s('display:flex;align-items:center;gap:6px;')}><span style={s('width:11px;height:11px;border-radius:3px;background:var(--green-200);')}></span>แผน</span>
                    <span style={s('display:flex;align-items:center;gap:6px;')}><span style={s('width:11px;height:11px;border-radius:3px;background:var(--green-600);')}></span>จริง</span>
                  </div>
                </div>
              </div>

              {/* ===================== ALERTS ===================== */}
              <div style={{ ...s('flex-direction:column;gap:10px;'), display: show.alerts }}>
                {alerts.map((a, i) => (
                  <div key={i} className="vn-card-hover" onClick={() => this.setState({ alertDetail: a })} style={s('display:flex;align-items:flex-start;gap:13px;background:var(--surface-card);border:1px solid var(--border-subtle);border-left:3px solid ' + a.accent + ';border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:13px 16px;cursor:pointer;')}>
                    <span style={s('flex:0 0 auto;width:34px;height:34px;border-radius:var(--radius-md);background:' + a.iconBg + ';color:' + a.accent + ';display:flex;align-items:center;justify-content:center;')}>{a.icon}</span>
                    <div style={s('flex:1;min-width:0;')}>
                      <div style={s('display:flex;align-items:center;gap:9px;margin-bottom:2px;')}>
                        <span style={s('font-size:13px;font-weight:600;color:var(--text-strong);')}>{a.title}</span>
                        <span style={s('font-size:10px;font-weight:600;padding:2px 8px;border-radius:var(--radius-pill);background:' + a.tagBg + ';color:' + a.tagFg + ';')}>{a.tag}</span>
                      </div>
                      <div style={s('font-size:12px;color:var(--text-body);line-height:1.45;')}>{a.body}</div>
                      <div style={s('font-size:10.5px;color:var(--text-faint);margin-top:4px;')}>{a.time}</div>
                    </div>
                  </div>
                ))}
              </div>

              {/* ===================== SETTINGS ===================== */}
              <div style={{ ...s('flex-direction:column;gap:16px;'), display: show.settings }}>
                {isAdmin ? (
                  <div style={s('display:grid;grid-template-columns:1fr 1fr;gap:16px;')}>
                    {settingPanels.map((p, i) => (
                      <div key={i} style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden;')}>
                        <div style={s('display:flex;align-items:center;gap:9px;padding:13px 18px;background:var(--green-50);border-bottom:1px solid var(--green-100);')}>
                          <span style={s('color:var(--green-600);')}>{p.icon}</span>
                          <span style={s('font-size:13px;font-weight:600;color:var(--text-strong);')}>{p.title}</span>
                        </div>
                        <div style={s('padding:16px 18px;display:grid;grid-template-columns:1fr 1fr;gap:13px;')}>
                          {p.fields.map((f, j) => (
                            <div key={j} style={{ ...s('display:flex;flex-direction:column;gap:5px;'), ...s(f.span) }}>
                              <label style={s('font-size:11px;font-weight:500;color:var(--text-muted);')}>{f.label} <span style={s('color:var(--text-faint);font-weight:400;')}>{f.labelEn}</span></label>
                              <input defaultValue={f.value} style={s('padding:8px 11px;font-family:' + f.mono + ';font-size:12.5px;color:var(--text-strong);background:var(--surface-card);border:1px solid var(--border-field);border-radius:var(--radius-md);outline:none;')} />
                            </div>
                          ))}
                        </div>
                      </div>
                    ))}
                  </div>
                ) : denied}
              </div>

              {/* ===================== USERS ===================== */}
              <div style={{ ...s('flex-direction:column;gap:14px;'), display: show.users }}>
                {isAdmin ? (
                  <>
                    <div style={s('display:flex;align-items:center;gap:10px;')}>
                      <span style={s('font-size:13px;font-weight:600;color:var(--text-strong);')}>{DS.users.length} ผู้ใช้งาน</span>
                      <button onClick={() => this.openUserModal(null)} style={s('margin-left:auto;padding:7px 16px;border:none;border-radius:var(--radius-md);background:var(--green-600);color:#fff;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:6px;')}>
                        {ic('plus', 15, '#fff')} เพิ่มผู้ใช้งาน
                      </button>
                    </div>
                    <div style={s('background:var(--surface-card);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);overflow:hidden;')}>
                      <table style={s('width:100%;border-collapse:collapse;font-size:12.5px;')}>
                        <thead>
                          <tr style={s('background:var(--surface-sunken);')}>
                            {[['ผู้ใช้งาน','padding:10px 16px;font-size:11px;font-weight:600;color:var(--text-muted);text-align:left;'],['สิทธิ์','padding:10px 14px;font-size:11px;font-weight:600;color:var(--text-muted);text-align:left;'],['สถานะ','padding:10px 14px;font-size:11px;font-weight:600;color:var(--text-muted);text-align:left;'],['จัดการ','padding:10px 16px;font-size:11px;font-weight:600;color:var(--text-muted);text-align:right;']].map(([label,sty],i) => (
                              <th key={i} style={s(sty)}>{label}</th>
                            ))}
                          </tr>
                        </thead>
                        <tbody>
                          {users.map((u, i) => (
                            <tr key={i} className="vn-row" style={s('border-top:1px solid var(--border-subtle);')}>
                              <td style={s('padding:10px 16px;')}>
                                <div style={s('display:flex;align-items:center;gap:10px;')}>
                                  <div style={s('width:32px;height:32px;border-radius:50%;background:' + u.avBg + ';display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#fff;flex:0 0 auto;')}>{u.initial}</div>
                                  <div>
                                    <div style={s('font-size:13px;font-weight:600;color:var(--text-strong);')}>{u.name}</div>
                                    <div style={s('font-size:11px;color:var(--text-muted);')}>{u.email}</div>
                                  </div>
                                </div>
                              </td>
                              <td style={s('padding:10px 14px;')}><span style={s('font-size:11px;font-weight:600;padding:2px 10px;border-radius:var(--radius-pill);background:' + u.roleBg + ';color:' + u.roleFg + ';')}>{u.roleLabel}</span></td>
                              <td style={s('padding:10px 14px;')}><span style={s('font-size:11px;font-weight:600;padding:2px 10px;border-radius:var(--radius-pill);background:' + u.stBg + ';color:' + u.stFg + ';')}>{u.status}</span></td>
                              <td style={s('padding:10px 16px;text-align:right;')}>
                                <button onClick={() => this.openUserModal(u)} style={s('padding:5px 12px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:11.5px;color:var(--text-body);font-family:inherit;margin-right:6px;')}>แก้ไข</button>
                                <button onClick={() => this.setState({ userDelConfirm: u })} style={s('padding:5px 12px;border:1px solid #fca5a5;border-radius:var(--radius-md);background:#fff1f2;cursor:pointer;font-size:11.5px;color:#dc2626;font-family:inherit;')}>ลบ</button>
                              </td>
                            </tr>
                          ))}
                        </tbody>
                      </table>
                    </div>
                  </>
                ) : denied}
              </div>

            </div>
          </main>
        </div>

        {/* ===== Farmer Detail ===== */}
        {S.farmerDetail && (() => {
          const f = S.farmerDetail;
          return (
            <div style={s('position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,0.72);display:flex;align-items:center;justify-content:center;')} onClick={e => { if(e.target===e.currentTarget) this.setState({farmerDetail:null}); }}>
              <div style={s('background:#ffffff;border-radius:var(--radius-lg);box-shadow:0 25px 60px rgba(0,0,0,0.35),0 8px 20px rgba(0,0,0,0.2);width:400px;max-width:95vw;overflow:hidden;')}>
                <div style={s('display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:#f0faf4;border-bottom:3px solid #1F8A56;')}>
                  <div>
                    <div style={s('font-size:14px;font-weight:700;color:#145C36;')}>{f.name}</div>
                    <div style={s('font-size:11px;color:#1F8A56;margin-top:1px;')}>{f.no}</div>
                  </div>
                  <button onClick={() => this.setState({farmerDetail:null})} style={s('background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:18px;')}>✕</button>
                </div>
                <div style={s('padding:4px 20px 4px;display:flex;flex-direction:column;gap:0;')}>
                  {[['กลุ่ม',f.group],['หมู่บ้าน',f.village],['หัวจ่าย',f.nozzle],['พื้นที่',f.rai+' ไร่'],['พืชที่ปลูก',f.crop],['สถานะ',f.stLabel]].map((r,ri) => (
                    <div key={ri} style={s('display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #eee;')}>
                      <span style={s('font-size:12px;color:#666;')}>{r[0]}</span>
                      <b style={s('font-size:13px;color:#111;font-family:var(--font-mono);font-weight:700;')}>{r[1]}</b>
                    </div>
                  ))}
                </div>
                <div style={s('padding:12px 20px 16px;display:flex;gap:8px;border-top:1px solid #eee;background:#f8f8f8;')}>
                  <button onClick={() => { this.setState({farmerDetail:null,page:'nozzles',nozGroup:f.group}); }} style={s('padding:6px 14px;border:1px solid var(--green-300);border-radius:var(--radius-md);background:var(--green-50);cursor:pointer;font-size:12px;color:var(--green-700);font-family:inherit;')}>ดูหัวจ่ายกลุ่มนี้</button>
                  <button onClick={() => this.setState({farmerDetail:null})} style={s('padding:6px 14px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:12px;color:#333;font-family:inherit;')}>ปิด</button>
                </div>
              </div>
            </div>
          );
        })()}

        {/* ===== Nozzle Detail ===== */}
        {S.nozzleDetail && (() => {
          const n = S.nozzleDetail;
          const stAccent = {flowing:'var(--green-600)',active:'var(--green-400)',maintenance:'var(--amber-status-600)',inactive:'var(--neutral-500)'};
          const stTh = {flowing:'กำลังส่งน้ำ',active:'พร้อมใช้งาน',maintenance:'ซ่อมบำรุง',inactive:'ไม่ใช้งาน'};
          const accent = stAccent[n.rawStatus] || 'var(--text-muted)';
          return (
            <div style={s('position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,0.72);display:flex;align-items:center;justify-content:center;')} onClick={e => { if(e.target===e.currentTarget) this.setState({nozzleDetail:null}); }}>
              <div style={s('background:#ffffff;border-radius:var(--radius-lg);box-shadow:0 25px 60px rgba(0,0,0,0.35),0 8px 20px rgba(0,0,0,0.2);width:380px;max-width:95vw;overflow:hidden;')}>
                <div style={s('display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:#f9f9f9;border-bottom:3px solid ' + accent + ';')}>
                  <div>
                    <div style={s('font-size:14px;font-weight:700;color:var(--text-strong);')}>{n.id}</div>
                    <div style={s('font-size:11px;color:var(--text-muted);margin-top:1px;')}>กลุ่ม {n.rawGroup}</div>
                  </div>
                  <button onClick={() => this.setState({nozzleDetail:null})} style={s('background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:18px;')}>✕</button>
                </div>
                <div style={s('padding:4px 20px 4px;display:flex;flex-direction:column;gap:0;')}>
                  {[['เจ้าของ',n.owner],['พื้นที่',n.area+' ไร่'],['กลุ่ม',n.rawGroup],['สถานะ',stTh[n.rawStatus]||n.rawStatus],['แรงดัน','2.8 bar'],['อัตราจ่าย','3.4 ลบ.ม./ชม.']].map((r,ri) => (
                    <div key={ri} style={s('display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #eee;')}>
                      <span style={s('font-size:12px;color:#666;')}>{r[0]}</span>
                      <b style={s('font-size:13px;font-family:var(--font-mono);font-weight:700;color:' + (r[0]==='สถานะ' ? accent : '#111') + ';')}>{r[1]}</b>
                    </div>
                  ))}
                </div>
                <div style={s('padding:12px 20px 16px;border-top:1px solid #eee;background:#f8f8f8;')}>
                  <button onClick={() => this.setState({nozzleDetail:null})} style={s('padding:6px 14px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:12px;color:#333;font-family:inherit;')}>ปิด</button>
                </div>
              </div>
            </div>
          );
        })()}

        {/* ===== Alert Detail ===== */}
        {S.alertDetail && (() => {
          const a = S.alertDetail;
          return (
            <div style={s('position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,0.72);display:flex;align-items:center;justify-content:center;')} onClick={e => { if(e.target===e.currentTarget) this.setState({alertDetail:null}); }}>
              <div style={s('background:#ffffff;border-radius:var(--radius-lg);box-shadow:0 25px 60px rgba(0,0,0,0.35),0 8px 20px rgba(0,0,0,0.2);width:420px;max-width:95vw;overflow:hidden;')}>
                <div style={s('display:flex;align-items:center;gap:12px;padding:16px 20px;border-bottom:3px solid ' + a.accent + ';background:#f9f9f9;')}>
                  <span style={s('width:36px;height:36px;border-radius:var(--radius-md);background:var(--surface-card);display:flex;align-items:center;justify-content:center;flex:0 0 auto;')}>{a.icon}</span>
                  <div style={s('flex:1;')}>
                    <div style={s('font-size:14px;font-weight:700;color:var(--text-strong);')}>{a.title}</div>
                    <div style={s('font-size:11px;color:var(--text-muted);margin-top:2px;')}>{a.tag} · {a.time}</div>
                  </div>
                  <button onClick={() => this.setState({alertDetail:null})} style={s('background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:18px;')}>✕</button>
                </div>
                <div style={s('padding:18px 20px;font-size:13.5px;color:#333;line-height:1.7;')}>{a.body}</div>
                <div style={s('padding:12px 20px 16px;display:flex;gap:8px;border-top:1px solid #eee;background:#f8f8f8;')}>
                  <button onClick={() => { this.showToast('รับทราบแล้ว'); this.setState({alertDetail:null}); }} style={s('padding:6px 16px;border:none;border-radius:var(--radius-md);background:' + a.accent + ';color:#fff;cursor:pointer;font-size:12px;font-family:inherit;font-weight:600;')}>รับทราบ</button>
                  <button onClick={() => this.setState({alertDetail:null})} style={s('padding:6px 14px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:12px;color:#333;font-family:inherit;')}>ปิด</button>
                </div>
              </div>
            </div>
          );
        })()}


        {/* ===== Bar Drill-Down Modal ===== */}
        {S.barDrillDown && (() => {
          const bd = S.barDrillDown;
          const mi = bd.monthIdx;
          const prev = mi > 0 ? months[mi - 1][1] : null;
          const diff = prev !== null ? bd.rawVal - prev : 0;
          const pct = Math.round(bd.rawVal / 52000 * 100);
          const ytd = months.slice(0, mi + 1).reduce((acc, m) => acc + m[1], 0);
          // Generate per-farmer water distribution (proportional to rai with month-seeded variation)
          const seed = mi * 7 + 13;
          const fRows = DS.farmers.map((f, fi) => {
            const rai = parseFloat(f.rai) || 8;
            const variation = 0.8 + ((seed * (fi + 1) * 31 % 100) / 100) * 0.4;
            return { ...f, vol: Math.round(rai * 28.5 * variation) };
          });
          const totalAssigned = fRows.reduce((a, r) => a + r.vol, 0);
          const scaleFactor = totalAssigned > 0 ? bd.rawVal / totalAssigned : 1;
          const scaledRows = fRows.map(r => ({ ...r, vol: Math.round(r.vol * scaleFactor) }))
            .sort((a, b) => b.vol - a.vol);
          const [drillSearch, setDrillSearch] = [S.barDrillSearch || '', v => this.setState({ barDrillSearch: v })];
          const filtered = scaledRows.filter(r =>
            !drillSearch || r.name.includes(drillSearch) || r.no.includes(drillSearch) || (r.group||'').includes(drillSearch)
          );
          return (
            <div style={s('position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,0.72);display:flex;align-items:center;justify-content:center;padding:16px;')} onClick={e => { if(e.target===e.currentTarget) this.setState({barDrillDown:null,barDrillSearch:''}); }}>
              <div style={s('background:#f4f7f5;border-radius:var(--radius-lg);box-shadow:0 30px 80px rgba(0,0,0,0.4);width:780px;max-width:98vw;max-height:90vh;display:flex;flex-direction:column;overflow:hidden;')}>
                {/* Header */}
                <div style={s('display:flex;align-items:center;justify-content:space-between;padding:16px 22px;background:var(--green-700);color:#fff;flex:0 0 auto;')}>
                  <div>
                    <div style={s('font-size:15px;font-weight:700;letter-spacing:.01em;')}>รายละเอียดการจ่ายน้ำ — {bd.label}</div>
                    <div style={s('font-size:11.5px;opacity:.8;margin-top:2px;')}>ปริมาณรวม {bd.rawVal.toLocaleString()} ลบ.ม. · เกษตรกร {filtered.length} ราย</div>
                  </div>
                  <button onClick={() => this.setState({barDrillDown:null,barDrillSearch:''})} style={s('background:rgba(255,255,255,0.2);border:none;cursor:pointer;color:#fff;font-size:18px;width:34px;height:34px;border-radius:var(--radius-md);display:flex;align-items:center;justify-content:center;')}>✕</button>
                </div>
                {/* KPI Cards */}
                <div style={s('display:grid;grid-template-columns:repeat(4,1fr);gap:10px;padding:14px 20px;flex:0 0 auto;background:var(--green-700);')}>
                  {[
                    ['ปริมาณน้ำ', bd.rawVal.toLocaleString() + ' ลบ.ม.', '#fff', 'rgba(255,255,255,0.15)'],
                    ['เทียบเดือนก่อน', prev !== null ? (diff >= 0 ? '+' : '') + diff.toLocaleString() + ' ลบ.ม.' : 'N/A', diff >= 0 ? '#86efac' : '#fca5a5', 'rgba(255,255,255,0.1)'],
                    ['% ของปีงบ', pct + '%', '#fde68a', 'rgba(255,255,255,0.1)'],
                    ['สะสม YTD', (ytd/1000).toFixed(1) + 'k ลบ.ม.', '#fff', 'rgba(255,255,255,0.1)'],
                  ].map((r, ri) => (
                    <div key={ri} style={s('background:' + r[3] + ';border-radius:var(--radius-md);padding:10px 13px;')}>
                      <div style={s('font-size:10px;color:rgba(255,255,255,0.7);margin-bottom:4px;font-weight:500;')}>{r[0]}</div>
                      <div style={s('font-size:15px;font-weight:700;color:' + r[2] + ';font-family:var(--font-mono);')}>{r[1]}</div>
                    </div>
                  ))}
                </div>
                {/* Search bar */}
                <div style={s('padding:12px 20px;background:#fff;border-bottom:1px solid var(--border-subtle);flex:0 0 auto;display:flex;align-items:center;gap:10px;')}>
                  <div style={s('position:relative;flex:1;')}>
                    <span style={s('position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--text-faint);display:flex;')}>{ic('search',14,'var(--text-faint)')}</span>
                    <input value={drillSearch} onChange={e => setDrillSearch(e.target.value)} placeholder="ค้นหาชื่อ / รหัส / กลุ่ม" style={s('width:100%;padding:8px 12px 8px 32px;font-family:inherit;font-size:12.5px;border:1px solid var(--border-field);border-radius:var(--radius-md);outline:none;color:var(--text-strong);background:var(--surface-card);')} />
                  </div>
                  <span style={s('font-size:12px;color:var(--text-muted);white-space:nowrap;')}>แสดง {filtered.length} / {scaledRows.length} ราย</span>
                </div>
                {/* Table */}
                <div style={s('overflow-y:auto;flex:1;')}>
                  <table style={s('width:100%;border-collapse:collapse;font-size:12.5px;')}>
                    <thead style={s('position:sticky;top:0;z-index:1;')}>
                      <tr style={s('background:#eef5f1;')}>
                        {[['#','padding:10px 12px;width:40px;text-align:center;'],['รหัส','padding:10px 12px;'],['ชื่อเกษตรกร','padding:10px 12px;'],['กลุ่ม','padding:10px 12px;'],['หัวจ่าย','padding:10px 12px;'],['พื้นที่ (ไร่)','padding:10px 12px;text-align:right;'],['ปริมาณน้ำ (ลบ.ม.)','padding:10px 14px;text-align:right;'],['% ของเดือน','padding:10px 14px;text-align:right;']].map(([l,st],ci) => (
                          <th key={ci} style={s(st+'font-size:11px;font-weight:600;color:var(--text-muted);text-align:' + (st.includes('right') ? 'right' : 'left') + ';')}>{l}</th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      {filtered.map((r, ri) => {
                        const pctOfMonth = bd.rawVal > 0 ? (r.vol / bd.rawVal * 100).toFixed(1) : '0.0';
                        const barW = Math.round(r.vol / bd.rawVal * 100);
                        return (
                          <tr key={ri} className="vn-row" style={s('border-top:1px solid var(--border-subtle);background:' + (ri%2===0?'#fff':'#fafcfb') + ';')}>
                            <td style={s('padding:9px 12px;text-align:center;font-size:11px;color:var(--text-faint);')}>{ri+1}</td>
                            <td style={s('padding:9px 12px;font-family:var(--font-mono);font-size:11.5px;color:var(--text-muted);')}>{r.no}</td>
                            <td style={s('padding:9px 12px;font-weight:500;color:var(--text-strong);')}>{r.name}</td>
                            <td style={s('padding:9px 12px;')}><span style={s('font-size:10.5px;font-weight:600;padding:2px 8px;border-radius:var(--radius-pill);background:' + (r.groupColor||'#ccc') + '22;color:' + (r.groupColor||'#666') + ';border:1px solid ' + (r.groupColor||'#ccc') + '44;')}>{r.group}</span></td>
                            <td style={s('padding:9px 12px;font-family:var(--font-mono);font-size:11.5px;color:var(--text-body);')}>{r.nozzle}</td>
                            <td style={s('padding:9px 12px;text-align:right;font-family:var(--font-mono);color:var(--text-body);')}>{r.rai}</td>
                            <td style={s('padding:9px 14px;text-align:right;')}>
                              <div style={s('display:flex;align-items:center;gap:8px;justify-content:flex-end;')}>
                                <div style={s('width:60px;height:5px;border-radius:3px;background:#e5e7eb;overflow:hidden;')}>
                                  <div style={s('height:100%;width:' + Math.min(barW,100) + '%;background:var(--green-500);border-radius:3px;')}></div>
                                </div>
                                <span style={s('font-family:var(--font-mono);font-weight:600;color:var(--text-strong);min-width:52px;text-align:right;')}>{r.vol.toLocaleString()}</span>
                              </div>
                            </td>
                            <td style={s('padding:9px 14px;text-align:right;font-family:var(--font-mono);font-size:11.5px;color:var(--text-muted);')}>{pctOfMonth}%</td>
                          </tr>
                        );
                      })}
                    </tbody>
                    <tfoot>
                      <tr style={s('background:#eef5f1;border-top:2px solid var(--green-200);')}>
                        <td colSpan="5" style={s('padding:10px 12px;font-size:12px;font-weight:700;color:var(--text-strong);')}>รวมทั้งหมด {filtered.length} ราย</td>
                        <td style={s('padding:10px 12px;text-align:right;font-family:var(--font-mono);font-weight:700;color:var(--text-strong);')}>{filtered.reduce((a,r)=>a+parseFloat(r.rai||0),0).toFixed(0)} ไร่</td>
                        <td style={s('padding:10px 14px;text-align:right;font-family:var(--font-mono);font-weight:700;color:var(--green-700);')}>{filtered.reduce((a,r)=>a+r.vol,0).toLocaleString()}</td>
                        <td style={s('padding:10px 14px;text-align:right;font-family:var(--font-mono);font-weight:700;color:var(--text-muted);')}>100%</td>
                      </tr>
                    </tfoot>
                  </table>
                </div>
              </div>
            </div>
          );
        })()}

        {/* ===== KPI Detail Modal ===== */}
        {S.kpiModal !== null && (() => {
          const kpiDefs = [
            { title: 'น้ำ Vinasse ส่งแล้ว', accent: 'var(--co-usa)', rows: [
                ['รอบปัจจุบัน', K.volume + ' ลบ.ม.'],
                ['เป้าหมายต่อรอบ', '40,000 ลบ.ม.'],
                ['% ความสำเร็จ', K.totalNozzles ? Math.round(parseFloat(String(K.volume).replace(/,/g,'')) / 40000 * 100) + '%' : '—'],
                ['อัตราการจ่าย', '864 ลบ.ม./ชม.'],
                ['ประมาณสิ้นสุด', 'อีก ~18 ชม.'],
                ['รอบสะสมปีนี้', '9 รอบ | 341,420 ลบ.ม.'],
              ]},
            { title: 'พื้นที่ได้รับน้ำ', accent: 'var(--green-600)', rows: [
                ['พื้นที่รอบนี้', K.totalRai + ' ไร่'],
                ['เป้าหมาย', '12,500 ไร่'],
                ['% ครอบคลุม', K.totalRai ? Math.round(parseInt(String(K.totalRai).replace(/,/g,'')) / 12500 * 100) + '%' : '—'],
                ['กลุ่มเกษตรกร', DS.groups.length + ' กลุ่ม'],
                ['เฉลี่ยต่อกลุ่ม', DS.groups.length ? Math.round(parseInt(String(K.totalRai).replace(/,/g,'')) / DS.groups.length) + ' ไร่' : '—'],
                ['พื้นที่สูงสุด', 'กลุ่ม 1 — 2,340 ไร่'],
              ]},
            { title: 'หัวจ่ายออนไลน์', accent: 'var(--blue-500)', rows: [
                ['ออนไลน์', K.nozzlesOnline + ' หัว'],
                ['ออฟไลน์/แจ้งเตือน', String(parseInt(String(K.totalNozzles).replace(/,/g,'')) - parseInt(String(K.nozzlesOnline).replace(/,/g,''))) + ' หัว'],
                ['กำลังส่งน้ำ', K.flowing + ' หัว'],
                ['พร้อมใช้งาน', K.ready + ' หัว'],
                ['ซ่อมบำรุง', K.maint + ' หัว'],
                ['ไม่ใช้งาน', K.inactive + ' หัว'],
              ]},
            { title: 'เกษตรกรใช้น้ำ', accent: 'var(--amber-500)', rows: [
                ['กำลังรับน้ำ', K.activeFarmers + ' ราย'],
                ['ลงทะเบียนทั้งหมด', K.totalFarmers + ' ราย'],
                ['% การใช้งาน', K.totalFarmers ? Math.round(K.activeFarmers / K.totalFarmers * 100) + '%' : '—'],
                ['รับน้ำใหม่รอบนี้', '5 ราย'],
                ['เกษตรกรชาวไร่', Math.round(K.totalFarmers * 0.72) + ' ราย'],
                ['เกษตรกรชาวนา', Math.round(K.totalFarmers * 0.28) + ' ราย'],
              ]},
            { title: 'รอบน้ำปัจจุบัน', accent: 'var(--co-uba)', rows: [
                ['รอบที่', String(K.round)],
                ['เริ่มต้น', K.roundStart || '10 มิ.ย. 2568'],
                ['ระยะเวลาเป้า', '7 วัน'],
                ['วันที่ผ่านไป', '6 วัน'],
                ['สถานะ', 'กำลังดำเนินการ'],
                ['รอบถัดไป', '17 มิ.ย. 2568'],
              ]},
          ];
          const def = kpiDefs[S.kpiModal] || kpiDefs[0];
          return (
            <div style={s('position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,0.72);display:flex;align-items:center;justify-content:center;')} onClick={e => { if(e.target===e.currentTarget) this.setState({kpiModal:null}); }}>
              <div style={s('background:#ffffff;border-radius:var(--radius-lg);box-shadow:0 25px 60px rgba(0,0,0,0.35),0 8px 20px rgba(0,0,0,0.2);width:380px;max-width:95vw;overflow:hidden;')}>
                <div style={s('display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:#f8faf9;border-bottom:3px solid ' + def.accent + ';')}>
                  <span style={s('font-size:14px;font-weight:700;color:var(--text-strong);')}>{def.title}</span>
                  <button onClick={() => this.setState({kpiModal:null})} style={s('background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:18px;line-height:1;')}>✕</button>
                </div>
                <div style={s('padding:4px 20px 4px;display:flex;flex-direction:column;gap:0;')}>
                  {def.rows.map((row, ri) => (
                    <div key={ri} style={s('display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid #eee;')}>
                      <span style={s('font-size:12px;color:#666;')}>{row[0]}</span>
                      <b style={s('font-size:13px;color:#111;font-family:var(--font-mono);font-weight:700;')}>{row[1]}</b>
                    </div>
                  ))}
                </div>
                <div style={s('padding:12px 20px 16px;background:#f8f8f8;border-top:1px solid #eee;')}>
                  <button onClick={() => this.setState({kpiModal: S.kpiModal > 0 ? S.kpiModal - 1 : 4})} style={s('padding:6px 14px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:12px;color:var(--text-body);font-family:inherit;margin-right:8px;')}>← ก่อนหน้า</button>
                  <button onClick={() => this.setState({kpiModal: S.kpiModal < 4 ? S.kpiModal + 1 : 0})} style={s('padding:6px 14px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:12px;color:var(--text-body);font-family:inherit;')}>ถัดไป →</button>
                </div>
              </div>
            </div>
          );
        })()}

        {/* ===== User Modal (Add/Edit) ===== */}
        {S.userModal && (() => {
          const m = S.userModal;
          const isEdit = !!m.id;
          const inp = (label, key, opts={}) => {
            const val = m[key] !== undefined ? m[key] : '';
            return (
              <div style={s('display:flex;flex-direction:column;gap:5px;')}>
                <label style={s('font-size:11px;font-weight:600;color:var(--text-muted);')}>{label}{opts.required && <span style={s('color:#dc2626;')}> *</span>}</label>
                {opts.type === 'select' ? (
                  <select value={val} onChange={e => this.setState({ userModal: { ...m, [key]: e.target.value } })} style={s('padding:8px 11px;font-size:13px;border:1px solid var(--border-field);border-radius:var(--radius-md);background:var(--surface-card);color:var(--text-strong);font-family:inherit;')}>
                    {opts.options.map(o => <option key={o[0]} value={o[0]}>{o[1]}</option>)}
                  </select>
                ) : (
                  <input type={opts.type||'text'} value={val} placeholder={opts.placeholder||''} onChange={e => this.setState({ userModal: { ...m, [key]: e.target.value } })} style={s('padding:8px 11px;font-size:13px;border:1px solid var(--border-field);border-radius:var(--radius-md);background:var(--surface-card);color:var(--text-strong);font-family:inherit;outline:none;')} />
                )}
              </div>
            );
          };
          return (
            <div style={s('position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,0.72);display:flex;align-items:center;justify-content:center;')} onClick={e => { if(e.target===e.currentTarget) this.setState({userModal:null}); }}>
              <div style={s('background:#ffffff;border-radius:var(--radius-lg);box-shadow:0 25px 60px rgba(0,0,0,0.35),0 8px 20px rgba(0,0,0,0.2);width:440px;max-width:95vw;overflow:hidden;')}>
                <div style={s('display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:#f0faf4;border-bottom:1px solid var(--green-100);')}>
                  <span style={s('font-size:14px;font-weight:700;color:#145C36;')}>{isEdit ? 'แก้ไขผู้ใช้งาน' : 'เพิ่มผู้ใช้งานใหม่'}</span>
                  <button onClick={() => this.setState({userModal:null})} style={s('background:none;border:none;cursor:pointer;color:var(--text-muted);font-size:18px;line-height:1;')}>✕</button>
                </div>
                <div style={s('padding:20px;display:flex;flex-direction:column;gap:13px;')}>
                  {inp('ชื่อผู้ใช้ (username)', 'username', { required: true, placeholder: 'somchai' })}
                  {inp('ชื่อ-นามสกุล', 'full_name_th', { placeholder: 'สมชาย วงศ์ใหญ่' })}
                  {inp('อีเมล', 'email', { placeholder: 'somchai@example.com' })}
                  {inp('รหัสผ่าน' + (isEdit?' (เว้นว่างเพื่อไม่เปลี่ยน)':''), 'password', { type:'password', required:!isEdit, placeholder: isEdit?'เว้นว่างไว้ถ้าไม่ต้องการเปลี่ยน':'กำหนดรหัสผ่าน' })}
                  {inp('สิทธิ์การใช้งาน', 'role', { type:'select', options:[['admin','Administrator'],['operator','Operator'],['viewer','Viewer']] })}
                  <div style={s('display:flex;flex-direction:column;gap:5px;')}>
                    <label style={s('font-size:11px;font-weight:600;color:var(--text-muted);')}>สถานะ</label>
                    <select value={m.is_active} onChange={e => this.setState({ userModal: { ...m, is_active: Number(e.target.value) } })} style={s('padding:8px 11px;font-size:13px;border:1px solid var(--border-field);border-radius:var(--radius-md);background:var(--surface-card);color:var(--text-strong);font-family:inherit;')}>
                      <option value={1}>ใช้งาน</option>
                      <option value={0}>พักงาน</option>
                    </select>
                  </div>
                </div>
                <div style={s('display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid #eee;background:#f8f8f8;')}>
                  <button onClick={() => this.setState({userModal:null})} style={s('padding:8px 18px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:13px;font-family:inherit;color:var(--text-body);')}>ยกเลิก</button>
                  <button onClick={() => this.saveUser()} disabled={S.userSaving} style={s('padding:8px 22px;border:none;border-radius:var(--radius-md);background:var(--green-600);color:#fff;cursor:pointer;font-size:13px;font-weight:600;font-family:inherit;opacity:' + (S.userSaving?'0.6':'1') + ';')}>{S.userSaving ? 'กำลังบันทึก…' : (isEdit ? 'บันทึกการแก้ไข' : 'เพิ่มผู้ใช้')}</button>
                </div>
              </div>
            </div>
          );
        })()}

        {/* ===== Delete Confirm Modal ===== */}
        {S.userDelConfirm && (() => {
          const u = S.userDelConfirm;
          return (
            <div style={s('position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,0.72);display:flex;align-items:center;justify-content:center;')} onClick={e => { if(e.target===e.currentTarget) this.setState({userDelConfirm:null}); }}>
              <div style={s('background:#ffffff;border-radius:var(--radius-lg);box-shadow:0 25px 60px rgba(0,0,0,0.35),0 8px 20px rgba(0,0,0,0.2);width:380px;max-width:95vw;overflow:hidden;')}>
                <div style={s('padding:20px 22px;')}>
                  <div style={s('font-size:15px;font-weight:700;color:var(--text-strong);margin-bottom:8px;')}>ยืนยันการลบผู้ใช้งาน</div>
                  <div style={s('font-size:13px;color:var(--text-body);')}>คุณต้องการลบ <strong>{u.name}</strong> ({u.email}) ออกจากระบบ? การดำเนินการนี้ไม่สามารถย้อนกลับได้</div>
                </div>
                <div style={s('display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;border-top:1px solid #eee;background:#f8f8f8;')}>
                  <button onClick={() => this.setState({userDelConfirm:null})} style={s('padding:8px 18px;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--surface-card);cursor:pointer;font-size:13px;font-family:inherit;color:var(--text-body);')}>ยกเลิก</button>
                  <button onClick={() => this.deleteUser(u.rawId)} style={s('padding:8px 22px;border:none;border-radius:var(--radius-md);background:#dc2626;color:#fff;cursor:pointer;font-size:13px;font-weight:600;font-family:inherit;')}>ลบผู้ใช้งาน</button>
                </div>
              </div>
            </div>
          );
        })()}

        {/* toast */}
        {S.toast ? (
          <div style={s('position:fixed;bottom:22px;right:22px;z-index:1200;display:flex;align-items:center;gap:9px;background:var(--surface-inverse);color:var(--text-on-inverse);padding:11px 17px;border-radius:var(--radius-md);box-shadow:var(--shadow-lg);font-size:12.5px;')}>{checkIcon}{S.toast}</div>
        ) : null}
        {loadingOverlay}
      </div>
    );
  }
}

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