// ==== SERVICE LAYER (support service.js BARU, fallback ke app.js lama) ====
  const svc = await import("../app.js");
  const { requireSession, apiFetch, apiFetchAsUser, brandDisplayName, getQueryParam } = svc;

  let SESSION = null;

  // ===== PERF & SANITIZE UTILS =====
  const NF_ID = new Intl.NumberFormat('id-ID');
  const rupiahFmt = (n) => "Rp " + NF_ID.format(Number(n || 0));

  const debounce = (fn, ms = 150) => {
    let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
  };
  const rafCommit = (fn) => { if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(fn); fn(); };

  // Escape HTML
  const esc = (v) => String(v ?? "")
    .replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")
    .replaceAll('"',"&quot;").replaceAll("'","&#39;");

  // [FIX] CSS.escape polyfill untuk browser lama
  if (!window.CSS) window.CSS = {};
  if (!CSS.escape) {
    CSS.escape = (s) => String(s).replace(/[^a-zA-Z0-9_\-]/g, ch => '\\' + ch);
  }

  // ===== TEKS STATIS (Bahasa Indonesia) =====
  const TEXT = {
    pageTitle: "Auto Withdraw",
    pageDescPrefix: "Daftar transaksi otomatis untuk brand ",
    ddTheme: "Tema",
    ddLogout: "Keluar",

    menuDashboard: "Dashboard",
    menuGlobalSheets: "Global Sheets",
    menuBrandMeta: "Brand Meta",
    menuProfiles: "Profiles",
    submenuProfilesBank: "Edit Bank",
    submenuProfilesEwallet: "Edit Ewallet",
    submenuProfilesPulsa: "Edit Pulsa",
    submenuProfilesWD: "Edit Withdraw",

    menuProses: "Proses",
    depBank: "Catat Bank",
    depEwallet: "Catat Ewallet",
    depPulsa: "Catat Pulsa",
    depWD: "Catat Withdraw",

    // ✅ NEW: Notifikasi
    menuNotifikasi: "Notifikasi",
    notifBank: "Bank",
    notifEwallet: "Ewallet",

    menuLaporan: "Laporan",
    repPendingan: "Pendingan",
    repMistake: "Mistake",

    menuAutoWD: "Auto Withdraw",
    awdWithdraw: "Withdraw",

    menuAdmin: "Admin",
    menuUsers: "Users",

    forbiddenTitle: "Akses Ditolak",
   forbiddenDesc: "Halaman ini memerlukan scope: autowd.",
    goHome: "Ke Dashboard",
  };

  // Helper kecil supaya kode lain tidak perlu diubah
  const T = (k) => TEXT[k] || k;

  // ===== STATE =====
  const activeBrand = (getQueryParam("brand") || "tiara").toLowerCase();

  // === helper: nama file unik untuk incoming.json ===
  function makeIncomingName(txId, brand) {
    const cleanId = String(txId || "tx").trim().replace(/[^\w\-]/g, "");
    const b = (brand || "tiara").toLowerCase();
    return `incoming-${b}-${cleanId}.json`;
  }
  const linkTambah  = document.getElementById("linkTambah");
  const linkSetting = document.getElementById("linkSetting");
  if (linkTambah)  linkTambah.href  = `tambah.html?brand=${activeBrand}`;
  if (linkSetting) linkSetting.href = `setting.html?brand=${activeBrand}`;

  const REQUIRED_SCOPE_PAGE = "autowd";
  const brandLogoMap = {
    tiara: "assets/img/tiaratoto.png",
    tts:  "assets/img/totosakti.png",
    ttm:  "assets/img/totomantap.png", // pastikan file ini ada
  };

  const brandBadgeText = {
    tiara: "TIARATOTO",
    tts:   "TOTOSAKTI",
    ttm:   "TOTOMANTAP",
  }[activeBrand] || "BRAND";

  function ensureBrandOn(hrefs) {
    document.querySelectorAll('a[href]').forEach(a => {
      const href = a.getAttribute('href') || "";
      if (!href || href.startsWith('#') || href.startsWith('javascript:')) return;
      const [path, query] = href.split('?');
      if (!hrefs.includes(path)) return;
      if (query && /(^|&)brand=/.test(query)) return;
      a.href = `${path}?brand=${activeBrand}`;
    });
  }
  ensureBrandOn(["tambah.html","setting.html"]);

  // RBAC util
  function normalizeScopes(src){
    if (Array.isArray(src)) return src.map(String).map(s=>s.trim().toLowerCase()).filter(Boolean);
    if (src && typeof src === "object") return Object.keys(src).filter(k => !!src[k]).map(k=>k.toLowerCase());
    if (typeof src === "string") return src.split(/[,\s]+/).map(s=>s.trim().toLowerCase()).filter(Boolean);
    return [];
  }
  function scopeForHref(href){
    if (!href) return null;
    if (href.includes("withdraw-awd")) return "autowd";
    if (href.includes("pendingan") || href.includes("mistake")) return "laporan";
    if (href.includes("global.html") || href.includes("brand.html") || href.includes("profile-edit.html")) return "brand";
    if (href.includes("bank.html") || href.includes("ewallet.html") || href.includes("pulsa.html") || href.includes("wd.html")) return "proses";
    // ✅ NEW: scope Notifikasi
    if (href.includes("notifbank") || href.includes("notifewallet")) return "notifikasi";
    return null;
  }
  function hasScope(name, { isSuperadmin, scopes }){
    if (isSuperadmin) return true;
    return scopes.includes((name||"").toLowerCase());
  }

  // ===== DOM REFS =====
  const els = {
    desktopLogo: document.getElementById("desktopBrandLogo"),
    mobileLogo:  document.getElementById("mobileBrandLogo"),
    sideMenuDesktop: document.getElementById("sideMenuDesktop"),
    sideMenuMobile:  document.getElementById("sideMenuMobile"),
    brandLabel: document.getElementById("brandLabel"),
    brandDropdown: document.getElementById("brandDropdown"),
    brandBtn: document.getElementById("brandBtn"),
    userBtn: document.getElementById("userMenuBtn"),
    userDropdown: document.getElementById("userDropdown"),
    topUser: document.getElementById("topUser"),
    themeToggleBtn: document.getElementById("themeToggleBtn"),
    ddThemeText: document.getElementById("ddThemeText"),
    ddLogoutText: document.getElementById("ddLogoutText"),
    logoutBtn: document.getElementById("logoutBtn"),
    hamburgerBtn: document.getElementById("hamburgerBtn"),
    mobileSidebar: document.getElementById("mobileSidebar"),
    closeSidebarBtn: document.getElementById("closeSidebarBtn"),
    sidebarOverlay: document.getElementById("sidebarOverlay"),
    pageTitle: document.getElementById("pageTitle"),
    brandBadge: document.getElementById("brandBadge"),
    pageDesc: document.getElementById("pageDesc"),
    q: document.getElementById("q"),
    // filter
    filterBtn: document.getElementById("filterBtn"),
    filterDropdown: document.getElementById("filterDropdown"),
    fDateStart: document.getElementById("fDateStart"),
    fDateEnd: document.getElementById("fDateEnd"),
    fBankSrc: document.getElementById("fBankSrc"),
    fNamaSrc: document.getElementById("fNamaSrc"),
    fBankDst: document.getElementById("fBankDst"),
    fOP: document.getElementById("fOP"),
    fStatus: document.getElementById("fStatus"),
    resetFilter: document.getElementById("resetFilter"),
    applyFilter: document.getElementById("applyFilter"),
    // table
    tbody: document.getElementById("tbody"),
    countInfo: document.getElementById("countInfo"),
    leftInfo: document.getElementById("leftInfo"),
    pagination: document.getElementById("pagination"),
    // forbidden
    forbiddenOverlay: document.getElementById("forbiddenOverlay"),
    forbiddenTitle: document.getElementById("forbiddenTitle"),
    forbiddenDesc: document.getElementById("forbiddenDesc"),
    goHomeBtnText: document.getElementById("goHomeBtnText"),
    // brand dropdown wrap
    brandPickerWrap: document.getElementById("brandPickerWrap"),
    // modal
    modal: document.getElementById("modal"),
    modalTitle: document.getElementById("modalTitle"),
    modalBody: document.getElementById("modalBody"),
    modalClose: document.getElementById("modalClose"),
  };

  function setModalWidth(mode){
    const card = document.getElementById("modalCard");
    if(!card) return;
    if(mode === "compact"){
      card.classList.remove("max-w-2xl");
      card.classList.add("max-w-md");
    } else {
      card.classList.remove("max-w-md");
      card.classList.add("max-w-2xl");
    }
  }

  // ===== ICON helper =====
  function iconSVG(name){
    if(name==="home") return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M3 12L12 3l9 9"/><path d="M9 21V12h6v9"/></svg>`;
    if(name==="sheets") return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="9" y1="4" x2="9" y2="22"/></svg>`;
    if(name==="brand")  return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><circle cx="19" cy="12" r="3"/><circle cx="5" cy="12" r="3"/></svg>`;
    if(name==="profile")return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
    if(name==="chat")   return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 15a4 4 0 0 1-4 4H7l-4 4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/></svg>`;
    if(name==="users")  return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M17 21v-2a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>`;
    if(name==="report") return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M9 3h6a2 2 0 0 1 2 2v16l-5-3-5 3V5a2 2 0 0 1 2-2z"/></svg>`;
    if(name==="zap")    return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`;
    // ✅ NEW: icon Notifikasi
    if(name==="bank")   return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M3 10h18"/><path d="M5 10v10"/><path d="M9 10v10"/><path d="M15 10v10"/><path d="M19 10v10"/><path d="M4 20h16"/><path d="M12 3l9 5H3l9-5z"/></svg>`;
    if(name==="wallet") return `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v3"/><path d="M21 12h-7a2 2 0 0 0 0 4h7v-4z"/><circle cx="16" cy="14" r="1"/></svg>`;
    return "";
  }

  // ===== Sidebar =====
  function sidebarClockCard(prefix){
    return `
      <div class="mb-4">
        <div class="rounded-lg border border-slate-300 dark:border-[#3f3f46]
                    bg-slate-50 dark:bg-[#2a2a2a]
                    text-[12px] leading-tight text-slate-700 dark:text-slate-200 p-3">
          <div class="grid grid-cols-[max-content_1ch_1fr] items-baseline gap-x-2 gap-y-1">
            <div class="text-slate-500 dark:text-slate-400 font-medium">Tanggal</div>
            <div class="text-slate-500 dark:text-slate-400">:</div>
            <div id="sidebarDate${prefix}" class="font-semibold text-slate-800 dark:text-slate-100">-</div>

            <div class="text-slate-500 dark:text-slate-400 font-medium">Waktu</div>
            <div class="text-slate-500 dark:text-slate-400">:</div>
            <div id="sidebarTime${prefix}" class="font-semibold text-slate-800 dark:text-slate-100">-</div>
          </div>
        </div>
      </div>
    `;
  }
  function updateSidebarClock(){
    if (document.hidden) return;
    const now = new Date();
    const weekdayId = new Intl.DateTimeFormat('id-ID', { timeZone:'Asia/Jakarta', weekday:'long' }).format(now);
    const parts = new Intl.DateTimeFormat('en-US', {
      timeZone:'Asia/Jakarta', year:'numeric', month:'numeric', day:'2-digit',
      hour:'2-digit', minute:'2-digit', second:'2-digit', hour12:false
    }).formatToParts(now);
    let dd="", mm="", yyyy="", hh="", mi="", ss="";
    parts.forEach(p=>{ if(p.type==="day") dd=p.value; if(p.type==="month") mm=p.value; if(p.type==="year") yyyy=p.value; if(p.type==="hour") hh=p.value; if(p.type==="minute") mi=p.value; if(p.type==="second") ss=p.value; });
    const bulanShort = ["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agu","Sep","Okt","Nov","Des"];
    const tanggalStr = `${weekdayId}, ${dd} ${bulanShort[parseInt(mm,10)-1] || mm} ${yyyy}`;
    const waktuStr   = `${hh}:${mi}:${ss}`;
    ["Desktop","Mobile"].forEach(prefix=>{
      const d = document.getElementById("sidebarDate"+prefix);
      const t = document.getElementById("sidebarTime"+prefix);
      if (d) d.textContent = tanggalStr;
      if (t) t.textContent = waktuStr;
    });
  }

  const brandMap = {
    tiara: {
      sections: [
        { labelKey: "menuDashboard", items: [{ textKey:"menuDashboard", href:`index.html?brand=tiara`, icon:"home", active:false }]},
        { labelKey: null, items: [
          { textKey:"menuGlobalSheets", href:`global.html?brand=tiara`, icon:"sheets", active:false },
          { textKey:"menuBrandMeta",   href:`brand.html?brand=tiara`,  icon:"brand",  active:false },
          { textKey:"menuProfiles", href:null, icon:"profile", active:false,
            submenu: [
              { textKey:"submenuProfilesBank",    channel:"bank" },
              { textKey:"submenuProfilesEwallet", channel:"ewallet" },
              { textKey:"submenuProfilesPulsa",   channel:"pulsa" },
              { textKey:"submenuProfilesWD",      channel:"wd" },
            ]},
        ]},
        { labelKey: "menuProses", items: [
          { textKey:"depBank",    href:`bank.html?brand=tiara`,    icon:"chat", active:false },
          { textKey:"depEwallet", href:`ewallet.html?brand=tiara`, icon:"chat", active:false },
          { textKey:"depPulsa",   href:`pulsa.html?brand=tiara`,   icon:"chat", active:false },
          { textKey:"depWD",      href:`wd.html?brand=tiara`,      icon:"chat", active:false },
        ]},
        { labelKey: "menuNotifikasi", items: [
          { textKey:"notifBank",    href:`notifbank.html?brand=tiara`,    icon:"bank",   active:false },
          { textKey:"notifEwallet", href:`notifewallet.html?brand=tiara`, icon:"wallet", active:false },
        ]},
        { labelKey: "menuLaporan", items: [
          { textKey:"repPendingan", href:`pendingan.html?brand=tiara`, icon:"report", active:false },
          { textKey:"repMistake",   href:`mistake.html?brand=tiara`,   icon:"report", active:false },
        ]},
        { labelKey: "menuAutoWD", items: [
          { textKey:"awdWithdraw", href:`withdraw-awd.html?brand=tiara`, icon:"zap", active:true },
        ]},
        { labelKey: "menuAdmin", items: [
          { textKey:"menuUsers", href:`users.html?brand=tiara`, icon:"users", active:false, requireSuperadmin:true },
        ]},
      ],
    },
    tts: {
      sections: [
        { labelKey: "menuDashboard", items: [{ textKey:"menuDashboard", href:`index.html?brand=tts`, icon:"home", active:false }]},
        { labelKey: null, items: [
          { textKey:"menuGlobalSheets", href:`global.html?brand=tts`, icon:"sheets", active:false },
          { textKey:"menuBrandMeta",   href:`brand.html?brand=tts`,  icon:"brand",  active:false },
          { textKey:"menuProfiles", href:null, icon:"profile", active:false,
            submenu: [
              { textKey:"submenuProfilesBank",    channel:"bank" },
              { textKey:"submenuProfilesEwallet", channel:"ewallet" },
              { textKey:"submenuProfilesPulsa",   channel:"pulsa" },
              { textKey:"submenuProfilesWD",      channel:"wd" },
            ]},
        ]},
        { labelKey: "menuProses", items: [
          { textKey:"depBank",    href:`bank.html?brand=tts`,    icon:"chat", active:false },
          { textKey:"depEwallet", href:`ewallet.html?brand=tts`, icon:"chat", active:false },
          { textKey:"depPulsa",   href:`pulsa.html?brand=tts`,   icon:"chat", active:false },
          { textKey:"depWD",      href:`wd.html?brand=tts`,      icon:"chat", active:false },
        ]},
        { labelKey: "menuNotifikasi", items: [
          { textKey:"notifBank",    href:`notifbank.html?brand=tts`,    icon:"bank",   active:false },
          { textKey:"notifEwallet", href:`notifewallet.html?brand=tts`, icon:"wallet", active:false },
        ]},
        { labelKey: "menuLaporan", items: [
          { textKey:"repPendingan", href:`pendingan.html?brand=tts`, icon:"report", active:false },
          { textKey:"repMistake",   href:`mistake.html?brand=tts`,   icon:"report", active:false },
        ]},
        { labelKey: "menuAutoWD", items: [
          { textKey:"awdWithdraw", href:`withdraw-awd.html?brand=tts`, icon:"zap", active:true },
        ]},
        { labelKey: "menuAdmin", items: [
          { textKey:"menuUsers", href:`users.html?brand=tts`, icon:"users", active:false, requireSuperadmin:true },
        ]},
      ],
    },
    ttm: {
      sections: [
        { labelKey: "menuDashboard", items: [{ textKey:"menuDashboard", href:`index.html?brand=ttm`, icon:"home", active:false }]},
        { labelKey: null, items: [
          { textKey:"menuGlobalSheets", href:`global.html?brand=ttm`, icon:"sheets", active:false },
          { textKey:"menuBrandMeta",   href:`brand.html?brand=ttm`,  icon:"brand",  active:false },
          { textKey:"menuProfiles", href:null, icon:"profile", active:false,
            submenu: [
              { textKey:"submenuProfilesBank",    channel:"bank" },
              { textKey:"submenuProfilesEwallet", channel:"ewallet" },
              { textKey:"submenuProfilesPulsa",   channel:"pulsa" },
              { textKey:"submenuProfilesWD",      channel:"wd" },
            ]},
        ]},
        { labelKey: "menuProses", items: [
          { textKey:"depBank",    href:`bank.html?brand=ttm`,    icon:"chat", active:false },
          { textKey:"depEwallet", href:`ewallet.html?brand=ttm`, icon:"chat", active:false },
          { textKey:"depPulsa",   href:`pulsa.html?brand=ttm`,   icon:"chat", active:false },
          { textKey:"depWD",      href:`wd.html?brand=ttm`,      icon:"chat", active:false },
        ]},
        { labelKey: "menuNotifikasi", items: [
          { textKey:"notifBank",    href:`notifbank.html?brand=ttm`,    icon:"bank",   active:false },
          { textKey:"notifEwallet", href:`notifewallet.html?brand=ttm`, icon:"wallet", active:false },
        ]},
        { labelKey: "menuLaporan", items: [
          { textKey:"repPendingan", href:`pendingan.html?brand=ttm`, icon:"report", active:false },
          { textKey:"repMistake",   href:`mistake.html?brand=ttm`,   icon:"report", active:false },
        ]},
        { labelKey: "menuAutoWD", items: [
          { textKey:"awdWithdraw", href:`withdraw-awd.html?brand=ttm`, icon:"zap", active:true },
        ]},
        { labelKey: "menuAdmin", items: [
          { textKey:"menuUsers", href:`users.html?brand=ttm`, icon:"users", active:false, requireSuperadmin:true },
        ]},
      ],
    },
  };
  function renderSidebar(brandKey, prefix, opts = {}) {
    const isSuperadmin = !!opts.isSuperadmin;
    const scopes = opts.scopes || [];
    const cfg = brandMap[brandKey] || brandMap["tiara"];
    const parentActiveCls = "bg-blue-50 text-blue-600 border-blue-200 dark:bg-blue-950/40 dark:text-blue-400 dark:border-blue-800";
    const parentNormalCls = "hover:bg-slate-100 hover:text-blue-600 border-transparent text-slate-700 dark:text-slate-200 dark:hover:bg-[#2a2a2a] dark:hover:text-blue-400";
    let html = sidebarClockCard(prefix);
    cfg.sections.forEach(section=>{
      const visibleItems = (section.items||[]).filter(it=>{
        if (it.requireSuperadmin && !isSuperadmin) return false;
        if (it.icon === "profile") return hasScope("brand", { isSuperadmin, scopes });
        const need = scopeForHref(it.href);
        if (!need) return true;
        return hasScope(need, { isSuperadmin, scopes });
      });
      if (!visibleItems.length) return;
      const title = section.labelKey
        ? T(section.labelKey).toUpperCase()
        : ({ tiara:"TIARATOTO", tts:"TOTOSAKTI", ttm:"TOTOMANTAP" }[brandKey] || "BRAND");
      html += `<div class="mb-4"><div class="text-[11px] uppercase font-semibold text-slate-400 dark:text-slate-500 tracking-wide mb-2">${title}</div><ul class="space-y-1">`;
      visibleItems.forEach(item=>{
        if(item.icon==="profile"){
          html += `
            <li>
              <button id="profilesToggle${prefix}" class="w-full flex items-center justify-between px-3 py-2 rounded-md font-medium border ${parentNormalCls}">
                <span class="flex items-center gap-2">${iconSVG(item.icon)}<span>${T(item.textKey)}</span></span>
                <span class="text-slate-500 dark:text-slate-400">
                  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" class="transition-transform" data-chevron>
                    <polyline points="6 9 12 15 18 9"/>
                  </svg>
                </span>
              </button>
              <ul id="profilesSub${prefix}" class="mt-1 ml-6 space-y-1 hidden">
                ${(item.submenu||[]).map(sub=>`
                  <li>
                    <a href="profile-edit.html?brand=${brandKey}&channel=${sub.channel}"
                       class="flex items-center gap-2 px-3 py-2 rounded-md font-medium border text-[12px] leading-tight ${parentNormalCls}">
                      <span class="w-2 h-2 rounded-full bg-current"></span>
                      <span>${T(sub.textKey)}</span>
                    </a>
                  </li>
                `).join("")}
              </ul>
            </li>
          `;
        } else {
          const active = !!item.active;
          const linkCls = active ? parentActiveCls : parentNormalCls;
          html += `
            <li>
              <a href="${item.href}" class="flex items-center gap-2 px-3 py-2 rounded-md font-medium border ${linkCls}">
                ${iconSVG(item.icon)}<span>${T(item.textKey)}</span>
              </a>
            </li>
          `;
        }
      });
      html += `</ul></div>`;
    });
    return html;
  }
  function attachSidebarToggle(prefix){
    const btn = document.getElementById("profilesToggle"+prefix);
    const sub = document.getElementById("profilesSub"+prefix);
    if(!btn || !sub) return;
    btn.addEventListener("click", ()=>{
      sub.classList.toggle("hidden");
      const chev = btn.querySelector("[data-chevron]");
      if(chev) chev.classList.toggle("rotate-180");
    }, { passive:true });
  }

  // ===== APPLY STATIC =====
  function applyStatic(session){
    const logoSrc = brandLogoMap[activeBrand] || brandLogoMap.tiara;
    const badge   = brandBadgeText;
    els.desktopLogo.src = logoSrc; els.desktopLogo.alt = badge;
    els.mobileLogo.src  = logoSrc; els.mobileLogo.alt  = badge;

    els.brandLabel.textContent = badge;
    els.brandBadge.textContent = badge;
    els.pageTitle.textContent  = T("pageTitle");
    els.pageDesc.textContent   = T("pageDescPrefix") + brandDisplayName(activeBrand) + ".";
    els.ddThemeText.textContent= T("ddTheme");
    els.ddLogoutText.textContent = T("ddLogout");
    els.forbiddenTitle.textContent = T("forbiddenTitle");
    els.forbiddenDesc.textContent  = T("forbiddenDesc");
    els.goHomeBtnText.textContent  = T("goHome");

    // Brand dropdown toggle
    els.brandBtn.addEventListener("click",(e)=>{
      e.stopPropagation();
      els.brandDropdown.classList.toggle("hidden");
      if(!els.userDropdown.classList.contains("hidden")) els.userDropdown.classList.add("hidden");
    });

    // User dropdown toggle
    els.userBtn.addEventListener("click",(e)=>{
      e.stopPropagation();
      els.userDropdown.classList.toggle("hidden");
      if(!els.brandDropdown.classList.contains("hidden")) els.brandDropdown.classList.add("hidden");
    });

    // Theme toggle
    els.themeToggleBtn.addEventListener("click", ()=>{
      const root=document.documentElement; const isDark=root.classList.toggle("dark");
      localStorage.setItem("theme", isDark?"dark":"light");
    });

    // Mobile sidebar open/close
    function openMobile(){ els.mobileSidebar.classList.remove("-translate-x-full"); els.sidebarOverlay.classList.remove("hidden"); }
    function closeMobile(){ els.mobileSidebar.classList.add("-translate-x-full"); els.sidebarOverlay.classList.add("hidden"); }
    els.hamburgerBtn.addEventListener("click",(e)=>{ e.stopPropagation(); openMobile(); });
    els.closeSidebarBtn.addEventListener("click", closeMobile);
    els.sidebarOverlay.addEventListener("click", closeMobile, { passive:true });

    // close dropdowns on outside click (brand/user/filter)
    document.addEventListener("click", (e)=>{
      if(!els.brandDropdown.classList.contains("hidden")){
        if(!els.brandPickerWrap.contains(e.target)) els.brandDropdown.classList.add("hidden");
      }
      if(!els.userDropdown.classList.contains("hidden")){
        if(!els.userBtn.contains(e.target) && !els.userDropdown.contains(e.target)){
          els.userDropdown.classList.add("hidden");
          els.langOptions?.classList.add("hidden");
        }
      }
      if (els.filterDropdown && !els.filterDropdown.classList.contains("hidden")) {
        if (!els.filterDropdown.contains(e.target) && !els.filterBtn.contains(e.target)) {
          els.filterDropdown.classList.add("hidden");
        }
      }
    }, { passive:true });

    // username
    els.topUser.textContent = session?.username || "admin";

    // Filter dropdown toggle
    els.filterBtn?.addEventListener("click", (e)=>{ e.stopPropagation(); els.filterDropdown?.classList.toggle("hidden"); });
  }

  function applySidebar(isSuperadmin, scopes){
    els.sideMenuDesktop.innerHTML = renderSidebar(activeBrand, "Desktop", { isSuperadmin, scopes });
    els.sideMenuMobile.innerHTML  = renderSidebar(activeBrand, "Mobile",  { isSuperadmin, scopes });
    attachSidebarToggle("Desktop");
    attachSidebarToggle("Mobile");
    updateSidebarClock(); setInterval(updateSidebarClock, 1000);
  }

  // ====== HELPERS (format & mapping) ======
  const BULAN_ID_SHORT = ["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agu","Sep","Okt","Nov","Des"];
  function toWIBDate(epochSec){
    const d = new Date((Number(epochSec)||0)*1000 + 7*3600*1000);
    const dd = String(d.getUTCDate()).padStart(2,'0');
    const mm = d.getUTCMonth()+1;
    const yyyy = d.getUTCFullYear();
    const hh = String(d.getUTCHours()).padStart(2,'0');
    const mi = String(d.getUTCMinutes()).padStart(2,'0');
    const ss = String(d.getUTCSeconds()).padStart(2,'0');
    return `${dd} ${BULAN_ID_SHORT[mm-1] || mm} ${yyyy} - ${hh}:${mi}:${ss}`;
  }
  const rupiah = (n)=> rupiahFmt(n);

  function mapStatusBackendToUI(s){
    const x = String(s||"").toLowerCase();
    if (x==="queued")    return "Pending";
    if (x==="processed") return "Processed";
    if (x==="approve")   return "Approve";
    if (x==="failed" || x==="canceled") return "Failed";
    return "Pending";
  }

  function statusPill(s){
    const x = String(s||"").toLowerCase();
    if (x==="pending")   return `<span class="pill pill-amber">PENDING</span>`;
    if (x==="processed") return `<span class="pill pill-emerald">PROCESSED</span>`;
    if (x==="approve")   return `<span class="pill pill-violet">APPROVE</span>`;
    if (x==="failed")    return `<span class="pill pill-rose">FAILED</span>`;
    return `<span class="pill pill-slate">${esc(s).toUpperCase()}</span>`;
  }

  // NEW: klasifikasi ewallet
  const EWALLET_1000 = ["GOPAY","LINKAJA"]; // -> ADM
  const EWALLET_0    = ["OVO","DANA"];      // -> FREE

  function routePills(tx){
    const src = String(tx.bank_src||"").toUpperCase();
    const dst = String(tx.bank_dst||"").toUpperCase();
    const layanan = String(tx.layanan||"").toLowerCase();

    if (EWALLET_1000.includes(dst)) return `<span class="pill pill-amber">ADM</span>`;
    if (EWALLET_0.includes(dst) || (src && dst && src===dst)) return `<span class="pill pill-emerald">FREE</span>`;
    return (layanan==="realtime")
      ? `<span class="pill pill-orange">REALTIME</span>`
      : `<span class="pill pill-blue">BI FAST</span>`;
  }

  // OP mapping
  let PROFILE_WD = { username_op_map:{}, op_default_case:"lower" };
  let OP_MAP_LOWER = {};
  function applyCaseBy(opCase, s){
    if (!s) return s;
    const mode = String(opCase||"").toLowerCase();
    if (mode === "lower") return s.toLowerCase();
    if (mode === "title") return s.replace(/\b\w/g, c=>c.toUpperCase());
    return s.toUpperCase();
  }
  function defaultOp3(fromStr){
    const base = String(fromStr||"usr").trim();
    const pick = base ? base.slice(0,3) : "usr";
    return applyCaseBy(PROFILE_WD.op_default_case || "lower", pick);
  }
  function computeOP(tx){
    const key = String(tx.op_user || "").toLowerCase();
    if (key && OP_MAP_LOWER[key]) {
      return applyCaseBy(PROFILE_WD.op_default_case, String(OP_MAP_LOWER[key]));
    }
    return defaultOp3(tx.user_id || tx.name_src || "");
  }

  // [PATCH 2A] Helper approve yang mencoba beberapa jalur API
  async function approveTx(id){
    const tryCalls = [
      { url: `${AWD_PREFIX}/${encodeURIComponent(id)}/approved`, method: "PATCH", json: null },
      { url: `${AWD_PREFIX}/${encodeURIComponent(id)}/approved`, method: "POST",  json: null },
      { url: `${AWD_PREFIX}/${encodeURIComponent(id)}/approve`,  method: "PATCH", json: null },
      { url: `${AWD_PREFIX}/${encodeURIComponent(id)}/approve`,  method: "POST",  json: null },
      { url: `${AWD_PREFIX}/${encodeURIComponent(id)}`,          method: "PUT",   json: { status:"approve", opUser: SESSION?.username || "" } },

      // fallback path lama (kalau namespace beda)
      { url: `/api/awithdraw/${encodeURIComponent(id)}/approved`, method: "POST", json: null },
      { url: `/api/awithdraw/${encodeURIComponent(id)}`,          method: "PUT",  json: { status:"approve", opUser: SESSION?.username || "" } },
    ];

    let lastErr = null;
    for (const t of tryCalls){
      try{
        const r = await apiFetchAsUser(t.url, { method: t.method, ...(t.json ? { json: t.json } : {}) });
        // Jika BE mengembalikan "sudah ada", tetap dianggap sukses (idempotent)
        if (!r || r.ok !== false) return true;
      }catch(e){ lastErr = e; }
    }
    throw lastErr || new Error("approve_failed");
  }

  // [FIX] Sinkronisasi ke Sheet (best-effort lewat beberapa endpoint umum)
  async function syncSheet(id){
    const calls = [
      { url: `${AWD_PREFIX}/${encodeURIComponent(id)}/sheet-sync`,  method: "POST" },
      { url: `${AWD_PREFIX}/${encodeURIComponent(id)}/append-sheet`,method: "POST" },
      { url: `/api/awithdraw/${encodeURIComponent(id)}/sheet-sync`, method: "POST" },
      { url: `/api/awd/sheets/sync`, method: "POST", json: { id } },
    ];
    for (const c of calls){
      try{
        await apiFetchAsUser(c.url, { method:c.method, ...(c.json ? { json:c.json } : {}) });
        return true;
      }catch(_){ /* lanjut variasi berikutnya */ }
    }
    return false;
  }

  // [PATCH] Setelah approve → polling status dengan refresh list (tanpa GET /tx/:id)
  async function ensureApprovedThenSynced(id, { pollMs=800, timeoutMs=20000 } = {}){
    const t0 = Date.now();
    for(;;){
      try {
        await fetchAndRender(); // << refresh list sehingga VIEW_TX terbarui
        const cur = getTxById(id);
        const ui  = cur ? mapStatusBackendToUI(cur.status) : null;
        if (ui === "Approve" || ui === "Processed") {
          try { await syncSheet(id); } catch(_){}
          return true;
        }
      } catch(_){}
      if (Date.now() - t0 > timeoutMs) {
        try { await syncSheet(id); } catch(_){}
        return false;
      }
      await new Promise(r=>setTimeout(r, pollMs));
    }
  }

  function layananTag(layanan){
    const x = String(layanan||"").toLowerCase();
    if (x==="bifast") return `<span class="tag bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-300 ml-2">BI FAST</span>`;
    if (x==="realtime") return `<span class="tag bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-300 ml-2">REALTIME</span>`;
    return "";
  }

  function showToast(message, ms=2600){
    const div = document.createElement("div");
    div.textContent = String(message || "");
    div.style.position = "fixed";
    div.style.right = "16px";
    div.style.bottom = "16px";
    div.style.zIndex = 9999;
    div.style.padding = "10px 12px";
    div.style.borderRadius = "10px";
    div.style.border = "1px solid rgba(148,163,184,.35)";
    div.style.background = document.documentElement.classList.contains("dark") ? "#1e1e1e" : "#ffffff";
    div.style.boxShadow = "0 10px 25px rgba(0,0,0,.08)";
    div.style.fontSize = "13px";
    div.style.color = "inherit";
    document.body.appendChild(div);
    setTimeout(()=>{ div.style.opacity="0"; div.style.transition="opacity .2s"; }, ms-200);
    setTimeout(()=> div.remove(), ms);
  }

  // ===== DATA & DERIVED =====
  const PER_PAGE = 50;
  let PAGE = 1;
  let CURRENT_TOTAL = 0;
  let CURRENT_PAGES = 1;
  let VIEW_TX = [];
  const getTxById = (id) => VIEW_TX.find(t => t.id === id);
  // skeleton hanya sekali
  let FIRST_LOAD = true;

  const UI2BE_STATUS = {
    "": "",
    "Pending": "queued",
    "Processed": "processed",
    "Approve": "approve",
    "Failed": "failed,canceled"
  };

  // [PATCH] OP alias: hormati username_op_map, abaikan alias "agent/device" dari BE
  const AGENT_WORDS_RE = /^(agent|device|robot|worker)$/i;

  function updateTxDerived(tx){
    // waktu
    tx._when      = tx.occurred_at || tx.created_at || 0;
    tx._whenLabel = toWIBDate(tx._when);

    // status UI
    tx._uiStatus  = mapStatusBackendToUI(tx.status);

    // Tentukan alias OP:
    let finalAlias = "";
    const rawAlias = tx.op_alias || tx.opAlias || "";
    const rawUser  = tx.op_user  || tx.opUser || "";

    if (rawAlias && !AGENT_WORDS_RE.test(String(rawAlias))) {
      finalAlias = applyCaseBy(PROFILE_WD.op_default_case || "lower", String(rawAlias));
    } else if (rawUser && OP_MAP_LOWER[String(rawUser).toLowerCase?.() ? String(rawUser).toLowerCase() : String(rawUser).toLowerCase()]) {
      finalAlias = applyCaseBy(
        PROFILE_WD.op_default_case || "lower",
        OP_MAP_LOWER[String(rawUser).toLowerCase()]
      );
    } else {
      finalAlias = defaultOp3(tx.user_id || tx.name_src || "");
    }

    tx._op = finalAlias;
    return tx;
  }

  const STATE = {
    q: "", dateStart: "", dateEnd: "",
    bankSrc: "", nameSrc: "", bankDst: "",
    op: "", statusUI: ""
  };

    // ===== PERSIST FILTER (localStorage per brand + halaman) =====
  const FILTER_KEY = `awd_filters_${activeBrand}`;

  function saveFilterState() {
    try {
      localStorage.setItem(FILTER_KEY, JSON.stringify(STATE));
    } catch (_) {
      // abaikan kalau quota penuh / private mode
    }
  }

  function loadFilterState() {
    try {
      const raw = localStorage.getItem(FILTER_KEY);
      if (!raw) return;
      const obj = JSON.parse(raw);
      if (!obj || typeof obj !== "object") return;

      STATE.q         = obj.q         || "";
      STATE.dateStart = obj.dateStart || "";
      STATE.dateEnd   = obj.dateEnd   || "";
      STATE.bankSrc   = obj.bankSrc   || "";
      STATE.nameSrc   = obj.nameSrc   || "";
      STATE.bankDst   = obj.bankDst   || "";
      STATE.op        = obj.op        || "";
      STATE.statusUI  = obj.statusUI  || "";

      // apply ke input2 form (kalau elemennya ada)
      if (els.q)          els.q.value          = STATE.q;
      if (els.fDateStart) els.fDateStart.value = STATE.dateStart;
      if (els.fDateEnd)   els.fDateEnd.value   = STATE.dateEnd;
      if (els.fBankSrc)   els.fBankSrc.value   = STATE.bankSrc;
      if (els.fNamaSrc)   els.fNamaSrc.value   = STATE.nameSrc;
      if (els.fBankDst)   els.fBankDst.value   = STATE.bankDst;
      if (els.fOP)        els.fOP.value        = STATE.op;
      if (els.fStatus)    els.fStatus.value    = STATE.statusUI;
    } catch (_) {
      // kalau gagal parse, anggap kosong
    }
  }

  function clearFilterState() {
    try {
      localStorage.removeItem(FILTER_KEY);
    } catch (_) {}
  }

  // ===== AUTO LOCK (kunci tombol "Auto Proses") =====
  // Sekarang kita simpan juga status saat tombol dikunci,
  // supaya bisa bedakan: dikunci waktu status masih Pending
  // atau sudah Failed.
  const LOCK_KEY = `awd_autolock_${activeBrand}`;

  function _readLock() {
    try {
      const raw = localStorage.getItem(LOCK_KEY);
      if (!raw) return {};
      const parsed = JSON.parse(raw);

      // Versi lama: array ["id1","id2",...]
      if (Array.isArray(parsed)) {
        const obj = {};
        parsed.forEach(id => {
          obj[String(id)] = { locked: true, statusWhenLocked: "" };
        });
        return obj;
      }

      // Versi baru: object { id: {locked, statusWhenLocked}, ... }
      if (parsed && typeof parsed === "object") return parsed;
      return {};
    } catch (_) {
      return {};
    }
  }

  let AUTO_LOCK = _readLock();

  function _saveLock() {
    try {
      localStorage.setItem(LOCK_KEY, JSON.stringify(AUTO_LOCK || {}));
    } catch (_) {}
  }

  function isAutoLocked(id) {
    const meta = AUTO_LOCK && AUTO_LOCK[String(id)];
    return !!(meta && meta.locked);
  }

  function getLockMeta(id) {
    return (AUTO_LOCK && AUTO_LOCK[String(id)]) || null;
  }

  function lockAuto(id, statusWhenLocked) {
    const key = String(id);
    if (!AUTO_LOCK) AUTO_LOCK = {};
    AUTO_LOCK[key] = {
      locked: true,
      statusWhenLocked: statusWhenLocked || ""
    };
    _saveLock();
  }

  function unlockAuto(id) {
    const key = String(id);
    if (!AUTO_LOCK) return;
    delete AUTO_LOCK[key];
    _saveLock();
  }

  // ===== APPROVE UX HALUS =====
  const APPROVING = new Set();
  const markApproving = (id)=> APPROVING.add(String(id));
  const unmarkApproving = (id)=> APPROVING.delete(String(id));

  // Sematkan OP saat ini ke server agar tidak tertimpa "agent"
  async function stickOpOnServer(tx) {
    try {
      const opUser = SESSION?.username || "";
      const aliasGuess =
        OP_MAP_LOWER[opUser.toLowerCase?.() ? opUser.toLowerCase() : String(opUser).toLowerCase()] ||
        defaultOp3(opUser);
      await apiFetchAsUser(`/api/awd/tx/${encodeURIComponent(tx.id)}`, {
        method: "PUT",
        json: { opUser, op_alias: applyCaseBy(PROFILE_WD.op_default_case || "lower", aliasGuess) },
      });
    } catch (e) {
      console.warn("stickOpOnServer failed:", e?.message || e);
    }
  }

  // Ambil status TX by id (GET /api/awd/tx/:id), fallback cari di VIEW_TX
  async function fetchTxStatusById(id) {
    try {
      const r = await apiFetch(`/api/awd/tx/${encodeURIComponent(id)}`);
      return r?.status || null;
    } catch (_) {
      const t = getTxById(id);
      return t?.status || null;
    }
  }

  // [PATCH] Pantau sampai PROCESSED/APPROVE: refresh list setiap siklus polling
  async function monitorTxOutcome(id, { pollMs = 4000, maxMs = 300000 } = {}) {
    const start = Date.now();
    for (;;) {
      try {
        await fetchAndRender(); // << penting: update VIEW_TX dulu
        const cur = getTxById(id);
        const ui  = cur ? mapStatusBackendToUI(cur.status) : null;

        if (ui === "Processed" || ui === "Approve") {
          // keep locked (tombol tetap nonaktif untuk yang sudah selesai)
          return "done";
        }
        if (ui === "Failed") {
          unlockAuto(id);               // buka lagi
          await fetchAndRender();
          return "failed";
        }
      } catch(_) {}

      if (Date.now() - start > maxMs) {
        unlockAuto(id);
        await fetchAndRender();
        showToast("⚠️ Timeout. Cek device/log.");
        return "timeout";
      }
      await new Promise(r => setTimeout(r, pollMs));
    }
  }

  function buildQueryFromState(){
    const sp = new URLSearchParams();
    sp.set("brand", activeBrand);
    sp.set("pageSize", String(PER_PAGE));
    sp.set("page", String(PAGE));
    sp.set("sortBy", "created_at");
    sp.set("order", "desc");

    if (STATE.q)          sp.set("q", STATE.q);
    if (STATE.dateStart)  sp.set("from", STATE.dateStart);
    if (STATE.dateEnd)    sp.set("to", STATE.dateEnd);
    if (STATE.bankSrc)    sp.set("bankSrc", STATE.bankSrc);
    if (STATE.nameSrc)    sp.set("nameSrc", STATE.nameSrc);
    if (STATE.bankDst)    sp.set("bankDst", STATE.bankDst);
    if (STATE.op)         sp.set("op", STATE.op.toLowerCase());
    const beStatus = UI2BE_STATUS[STATE.statusUI] || "";
    if (beStatus)         sp.set("status", beStatus);

    return sp.toString();
  }

  let REQ = { tx:0, accounts:0, distinct:0 };

  async function loadDistinctOptions(){
    const rid = ++REQ.distinct;
    try{
      const r = await apiFetch(`/api/awd/tx/distinct?brand=${activeBrand}`);
      if (rid !== REQ.distinct) return;
      const bankSrc = (r?.bankSrc||[]);
      const nameSrc = (r?.nameSrc||[]);
      const bankDst = (r?.bankDst||[]);
      const opList  = (r?.op||[]);

      const setOpts = (el, arr) => {
        const cur = el.value;
        el.innerHTML = `<option value="">Semua</option>` + arr.map(v=>`<option>${esc(v||"")}</option>`).join("");
        if (cur && arr.includes(cur)) el.value = cur;
      };

      setOpts(els.fBankSrc, bankSrc);
      setOpts(els.fNamaSrc, nameSrc);
      setOpts(els.fBankDst, bankDst);
      setOpts(els.fOP,      opList.map(s => applyCaseBy(PROFILE_WD?.op_default_case||"lower", s||"")));
    }catch(_){ /* diamkan */ }
  }

  function renderTablePage(){
    const rows = VIEW_TX;

    rafCommit(()=>{
      els.tbody.innerHTML = rows.map(tx=>{
        const uiStatus = tx._uiStatus;
        const isApproving = APPROVING.has(String(tx.id));
        const showProofOnly = !isApproving && (uiStatus==="Processed");
        const showNothing   = !isApproving && (uiStatus==="Approve");

        const bankSrc = esc(tx.bank_src);
        const nameSrc = esc(tx.name_src || "-");
        const bankDst = esc(tx.bank_dst);
        const noDst   = esc(tx.no_dst);
        const nameDst = esc(tx.name_dst);
        const userId  = esc(tx.user_id);
        const opLab   = esc(tx.op_alias || tx._op || "-");
        const locked  = isAutoLocked(tx.id);

        return `
          <tr class="hover:bg-slate-50/60 dark:hover:bg-[#1a1a1a]">
            <td class="px-3 py-2 whitespace-nowrap font-semibold">${opLab}</td>
            <td class="px-3 py-2 whitespace-nowrap">${esc(tx._whenLabel)}</td>
            <td class="px-3 py-2">
              <div class="flex flex-col">
                <div class="whitespace-nowrap">
                  ${bankSrc}
                  <span class="ml-2 inline-flex gap-1 align-middle">${routePills(tx)}</span>
                </div>
                <div class="mt-0.5 text-[12px] text-slate-500 dark:text-slate-400 leading-tight">
                  ${nameSrc}
                </div>
              </div>
            </td>
            <td class="px-3 py-2 whitespace-nowrap">${bankDst}</td>
            <td class="px-3 py-2 whitespace-nowrap">${noDst}</td>
            <td class="px-3 py-2">${nameDst}</td>
            <td class="px-3 py-2 whitespace-nowrap">${userId}</td>
            <td class="px-3 py-2 text-right font-semibold tabular-nums">${rupiah(tx.amount)}</td>

            <!-- STATUS: SELALU pill normal, TANPA spinner saat approving -->
            <td class="px-3 py-2">${statusPill(uiStatus)}</td>

            <!-- AKSI: spinner hanya di sini saat approving -->
            <td class="px-3 py-2">
              ${
                isApproving ? `
                  <div class="flex items-center gap-2 text-slate-500">
                    <span class="inline-block w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin"></span>
                    <span class="text-[12px]">Menyimpan…</span>
                  </div>
                ` : (
                  showNothing ? `<div class="text-slate-400">—</div>` : `
                  <div class="flex items-center gap-1 text-slate-600 dark:text-slate-300">
                    ${showProofOnly ? `
                      <button class="btn-icon-glow btn-blue" title="Bukti Transfer" data-act="proof" data-id="${esc(tx.id)}">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor"
                          stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
                          <path d="M14 2H6a2 2 0 0 0-2 2v16l4-4h10a2 2 0 0 0 2-2V6a4 4 0 0 0-4-4z"/>
                          <line x1="8" y1="9" x2="16" y2="9"/><line x1="8" y1="13" x2="14" y2="13"/>
                        </svg>
                      </button>
                    ` : `
                      <button class="${locked ? 'btn-icon-glow btn-blue is-disabled' : 'btn-icon-glow btn-blue'}"
                              ${locked ? 'disabled aria-disabled="true" data-busy="1"' : ''}
                              title="Auto Proses" data-act="auto" data-id="${esc(tx.id)}">
                        ${
                          locked
                            ? `
                              <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18"
                                  viewBox="0 0 24 24" class="animate-spin">
                                <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" opacity=".25"></circle>
                                <path d="M22 12a10 10 0 0 1-10 10" fill="none" stroke="currentColor" stroke-width="2"></path>
                              </svg>
                            `
                            : `
                              <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18"
                                  fill="none" stroke="currentColor" stroke-width="1.6"
                                  stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
                                <polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
                                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
                              </svg>
                            `
                        }
                      </button>
                      <button class="btn-icon-glow btn-amber" title="Edit" data-act="edit" data-id="${esc(tx.id)}">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
                      </button>
                      <button class="btn-icon-glow btn-emerald" title="Ceklis / Approve" data-act="approve" data-id="${esc(tx.id)}">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H7l-4 4V7a2 2 0 0 1 2-2h7"/></svg>
                      </button>
                      <button class="btn-icon-glow btn-rose" title="Hapus" data-act="delete" data-id="${esc(tx.id)}">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>
                      </button>
                      ${uiStatus==="Failed" ? `
                        <button class="btn-icon-glow btn-slate" title="LOG Gagal" data-act="log" data-id="${esc(tx.id)}">
                          <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M3 3h18"/><path d="M7 3v18"/><path d="M3 7h18"/><path d="M13 13h6"/><path d="M13 17h6"/><path d="M13 9h6"/></svg>
                        </button>
                      ` : ``}
                    `}
                  </div>`
                )
              }
            </td>
          </tr>
        `;
      }).join("");

      // TOTAL WD halaman aktif
      const totalPageAmount = rows.reduce((sum, tx) => sum + Number(tx.amount || 0), 0);
      els.countInfo.textContent = `TOTAL WD: ${rupiah(totalPageAmount)}`;

      const shown = rows.length;
      if (CURRENT_TOTAL===0){
        els.leftInfo.textContent = `Menampilkan 0 sampai 0 dari 0 Withdraw`;
      } else {
        const startIdx = (PAGE-1)*PER_PAGE + 1;
        const endIdx   = (PAGE-1)*PER_PAGE + shown;
        els.leftInfo.textContent = `Menampilkan ${startIdx} sampai ${endIdx} dari ${CURRENT_TOTAL} Withdraw`;
      }
      renderPagination(CURRENT_TOTAL);
    });
  }

  function renderPagination(total){
    const pages = Math.max(1, Math.ceil(total / PER_PAGE));
    const wrap = els.pagination;
    wrap.innerHTML = "";

    function makeBtn(lbl, disabled, onClick, isActive=false){
      const b = document.createElement("button");
      b.className = `px-2.5 py-1 rounded-md border text-[12px] ${isActive ? 'bg-blue-600 text-white border-blue-600' : 'border-slate-300 dark:border-[#3f3f46] hover:bg-slate-100 dark:hover:bg-[#2a2a2a]'}`;
      b.textContent = lbl;
      if (disabled){ b.disabled = true; b.classList.add("opacity-50","cursor-not-allowed"); }
      else if (onClick){ b.addEventListener("click", onClick, { passive:true }); }
      wrap.appendChild(b);
    }

    makeBtn("<< Prev", PAGE<=1, async ()=>{
      PAGE = Math.max(1, PAGE-1);
      await fetchAndRender();
    });

    const windowSize = 7;
    const last = pages;
    let start = Math.max(1, PAGE - Math.floor(windowSize/2));
    let end   = start + windowSize - 1;
    if (end > last){ end = last; start = Math.max(1, end-windowSize+1); }

    if (start>1){ makeBtn("1", false, async ()=>{ PAGE=1; await fetchAndRender(); }, PAGE===1); if (start>2){ const dots=document.createElement("span"); dots.className="px-1 text-[12px] text-slate-500"; dots.textContent="..."; wrap.appendChild(dots); } }

    for (let p=start; p<=end; p++){
      makeBtn(String(p), false, async ()=>{ PAGE=p; await fetchAndRender(); }, PAGE===p);
    }

    if (end<last){ if (end<last-1){ const dots=document.createElement("span"); dots.className="px-1 text-[12px] text-slate-500"; dots.textContent="..."; wrap.appendChild(dots); } makeBtn(String(last), false, async ()=>{ PAGE=last; await fetchAndRender(); }, PAGE===last); }

    makeBtn("Next >>", PAGE>=pages, async ()=>{
      PAGE = Math.min(pages, PAGE+1);
      await fetchAndRender();
    });
  }

  // ===== SKELETON =====
  function renderSkeletonRows(n=6){
    const widths = ["w-8","w-24","w-28","w-20","w-24","w-24","w-16","w-24","w-16","w-24"];
    const cols = 10;
    let html = "";
    for(let i=0;i<n;i++){
      html += `<tr class="animate-pulse">
        ${Array.from({length:cols}).map((_,idx)=>`
          <td class="px-3 py-2">
            <span class="skel ${widths[idx%widths.length]} block"></span>
          </td>
        `).join("")}
      </tr>`;
    }
    els.tbody.innerHTML = html;
    els.leftInfo.textContent = "Memuat data...";
  }

  // ====== NEW: INTEGRASI WORKER autowd.js (RPC + fallback) ======
  let AWD_WORKER = null;
  let WORKER_READY = false;
  let WORKER_REQ_ID = 0;
  const WORKER_PENDING = new Map();

  function initAwdWorker(){
    try {
        const tryUrls = [
        new URL("../worker/autowd.js", import.meta.url), // => panelbot/assets/worker/autowd.js
        new URL("../../autowd.js", import.meta.url),     // => panelbot/autowd.js (fallback)
        ];

        for (const u of tryUrls) {
        try {
            try { AWD_WORKER = new Worker(u, { type: "module" }); }
            catch { AWD_WORKER = new Worker(u); }
            break;
        } catch (_) {}
        }
        if (!AWD_WORKER) return;

      AWD_WORKER.addEventListener("message", (ev)=>{
        const msg = ev.data || {};
        if (msg && msg.type === "ready") {
          WORKER_READY = true;
          console.log("[AWD_WORKER] READY");
          return;
        }
        if (Object.prototype.hasOwnProperty.call(msg, "id")) {
          const waiter = WORKER_PENDING.get(msg.id);
          if (!waiter) return;
          WORKER_PENDING.delete(msg.id);
          if (msg.ok) waiter.resolve(msg.result);
          else waiter.reject(new Error(msg.error || "worker_error"));
        }
      });

      AWD_WORKER.addEventListener("error", (e)=>{
        WORKER_READY = false;
        console.error("[AWD_WORKER] script error:", e);
        showToast("Worker error: periksa path/mime autowd.js di Network tab.");
      });
      AWD_WORKER.addEventListener("messageerror", (e)=>{
        console.error("[AWD_WORKER] messageerror:", e);
      });

      AWD_WORKER.postMessage({ type:"hello" });

      // Watchdog: info jika belum ready
      setTimeout(()=>{
        if (!WORKER_READY) {
          console.warn("[AWD_WORKER] belum READY (cek Network → autowd.js 200 & MIME javascript)");
        }
      }, 2000);
    } catch(e) {
      console.error("[AWD_WORKER] init failed:", e);
    }
  }

  function callWorker(method, params, timeoutMs=15000){
    if (!AWD_WORKER || !WORKER_READY) return Promise.reject(new Error("worker_not_ready"));
    const id = ++WORKER_REQ_ID;
    return new Promise((resolve, reject)=>{
      WORKER_PENDING.set(id, { resolve, reject });
      AWD_WORKER.postMessage({ id, method, params });
      setTimeout(()=>{ if (WORKER_PENDING.has(id)) { WORKER_PENDING.delete(id); reject(new Error("worker_timeout")); } }, timeoutMs);
    });
  }

  // [PATCH 3B] Worker: dukung relaxDevice agar header x-panel-id tidak dikirim
  async function startAutoJobViaWorker(txId, { relaxDevice = false } = {}){
    if (!AWD_WORKER || !WORKER_READY) throw new Error("worker_not_ready");

    const filedropUrl = (localStorage.getItem("awd_filedrop_url") || "").trim();
    if (!filedropUrl) throw new Error("filedrop_url_missing");

    const payload = {
      txId,
      brand: activeBrand,
      panelId: PANEL_ID || localStorage.getItem("panel_id") || null,
      username: (SESSION?.username && String(SESSION.username).trim()) || localStorage.getItem("last_user") || "anon",
      filedropUrl,
      suggestName: makeIncomingName(txId, activeBrand),
      relaxDevice: !!relaxDevice, // ← penting
    };

    const res = await callWorker("startJob", payload, 20000);
    return res; // { ok:true }
  }

  // [PATCH 3F] Legacy HTTP: kirim allowAnyDevice saat retry Failed
  async function startAutoJobLegacyHttp(txId){
    const tx = getTxById(txId);
    const relax = needRelaxDevice(tx);

    const res = await apiFetchAsUser("/api/awd/jobs/start", {
      method: "POST",
      headers: { "x-panel-id": PANEL_ID || "" },
      json: { txId, brand: activeBrand, opUser: SESSION?.username || "", allowAnyDevice: !!relax }
    }).catch(e => ({ error: "network", detail: String(e?.message||e) }));

    if (!res || res.error){
      const msg =
        res?.message
        || (res?.error==="device_not_ready" ? "Device belum aktif/terkait panel ini."
        :  res?.error==="panel_not_active"  ? "Panel belum aktif (register/heartbeat) di brand ini."
        :  res?.error==="template_not_found"? "Template YAML belum diset untuk rute ini."
        :  res?.error==="network"           ? "Jaringan bermasalah."
        :  "Gagal membuat job worker.");
      throw new Error(msg);
    }
    return res;
  }

  // Endpoint yang valid untuk incoming.json (urutkan dari yg paling mungkin)
  const AWD_ENDPOINTS = [
    (txId, brand) => `/awithdraw/${encodeURIComponent(txId)}/incoming.json?brand=${encodeURIComponent(brand)}`,
    (txId, brand) => `/api/awithdraw/${encodeURIComponent(txId)}/incoming.json?brand=${encodeURIComponent(brand)}`,
    (txId, brand) => `/api/awd/tx/${encodeURIComponent(txId)}/incoming.json?brand=${encodeURIComponent(brand)}`,
  ];

  const AWD_PREFIX = "/api/awd/tx";

  // [PATCH 3D] Fetch incoming.json; untuk relaxDevice=true, sertakan x-relax-device: 1, dan jangan include x-panel-id
  async function fetchIncomingJson(txId, relaxDevice = false) {
    let lastErr = null;
    for (const build of AWD_ENDPOINTS) {
      const url = build(txId, activeBrand);
      const headers = relaxDevice ? {
        "x-panel-user": (SESSION?.username && String(SESSION.username).trim()) || "anon",
        "x-relax-device": "1", // [PATCH]
      } : {
        "x-panel-id": PANEL_ID || "",
        "x-panel-user": (SESSION?.username && String(SESSION.username).trim()) || "anon",
      };
      const res = await apiFetchAsUser(url, { method: "GET", headers }).catch(e => ({ error: String(e?.message || e) }));
      if (res && !res.error) return res;
      lastErr = res?.message || res?.error || lastErr;
    }
    throw new Error(lastErr || "Gagal ambil incoming.json");
  }

  // [PATCH 3C] Download incoming.json; kalau relaxDevice=true, sertakan x-relax-device: 1 & jangan kirim x-panel-id
  async function downloadIncomingJson(txId, suggestName, relaxDevice = false) {
    let lastErr = null;
    const name = suggestName || makeIncomingName(txId, activeBrand);
    for (const build of AWD_ENDPOINTS) {
      const url = build(txId, activeBrand);
      try {
        const r = await fetch(url, {
          credentials: "include",
          headers: relaxDevice ? {
            "x-panel-user": (SESSION?.username && String(SESSION.username).trim()) || "anon",
            "x-relax-device": "1", // [PATCH]
          } : {
            "x-panel-id": PANEL_ID || "",
            "x-panel-user": (SESSION?.username && String(SESSION.username).trim()) || "anon",
          },
        });
        if (r.ok) {
          const blob = await r.blob();
          const a = document.createElement("a");
          const objUrl = URL.createObjectURL(blob);
          a.href = objUrl;
          a.download = name;
          document.body.appendChild(a);
          a.click();
          a.remove();
          setTimeout(() => URL.revokeObjectURL(objUrl), 4000);
          return;
        }
        lastErr = (await r.text().catch(() => r.statusText)) || lastErr;
      } catch (e) {
        lastErr = e?.message || lastErr;
      }
    }
    alert("Gagal buat file incoming.json: " + (lastErr || "unknown"));
  }

  // [PATCH 3A] Saat status terakhir Failed → relax device
  function needRelaxDevice(tx){
    return String(tx?._uiStatus || "").toLowerCase() === "failed";
  }

  // === LEGACY: Auto Proses = SELALU unduh incoming.json (tanpa worker/polling) ===
  async function startAutoJob(txId){
    const currentTx = getTxById(txId) || { id: txId };
    const relax = needRelaxDevice(currentTx);
    await downloadIncomingJson(txId, makeIncomingName(txId, activeBrand), relax);
    // Selalu return false (tidak menjalankan monitor/polling otomatis)
    return false;
  }

  // ===== ACTIONS =====
  async function handleAction(act, id){
    const tx = getTxById(id);

    if (act==="edit"){
      return editModal(tx || { id });
    }

    // === APPROVE: spinner hanya di tombol, tanpa polling ===
    if (act === "approve"){
      const btnEl = document.querySelector(`button[data-act="approve"][data-id="${CSS.escape(id)}"]`);
      const setBusy = (on) => {
        if (!btnEl) return;
        if (on) { btnEl.setAttribute("disabled",""); btnEl.classList.add("is-disabled"); }
        else    { btnEl.removeAttribute("disabled"); btnEl.classList.remove("is-disabled"); }
      };

      return confirmModal("approve", "Apa kamu ingin approve data?", async ()=>{
        try {
          // UX: langsung tandai APPROVING di baris ini
          markApproving(id);
          renderTablePage();

          setBusy(true);
          await approveTx(id);
          try { await syncSheet(id); } catch(_){}

          // hentikan indikator lalu refresh SEKALI
          unmarkApproving(id);
          await fetchAndRender();
          showToast("✅ Data berhasil di-approve.");
        } catch(e){
          unmarkApproving(id);
          alert("Gagal approve: " + (e?.message || e));
        } finally {
          setBusy(false);
        }
      });
    }

    if (act==="delete"){
      return confirmModal("delete", "Apa kamu ingin menghapus data?", async ()=>{
        try {
          await apiFetchAsUser(`${AWD_PREFIX}/${encodeURIComponent(id)}`, { method:"DELETE" });
        } catch(_) {
          await apiFetchAsUser(`/api/awd/tx/${encodeURIComponent(id)}`, { method:"DELETE" });
        }
        await loadDistinctOptions();
        await fetchAndRender();
      });
    }

    // === AUTO: selalu unduh incoming.json (tanpa monitor/polling) ===
    if (act === "auto") {
      const tx = getTxById(id) || { id };

      // 1) Kunci tombol segera (gelap & tidak bisa diklik)
      //    dan catat status saat ini (Pending / Failed / dst)
      lockAuto(id, tx._uiStatus || mapStatusBackendToUI(tx.status));
      renderTablePage();

      try {
        // 2) Sematkan OP ke server agar tidak berubah ke "agent"
        await stickOpOnServer(tx);

        // 3) LEGACY: langsung buat & unduh incoming.json dari D1
        await downloadIncomingJson(id, makeIncomingName(id, activeBrand), needRelaxDevice(tx));
        showToast("📄 incoming.json diunduh. Silakan drop ke device sesuai template.");
      } catch (e) {
        // Error → buka lagi tombol agar bisa coba ulang
        unlockAuto(id);
        renderTablePage();
        showToast("Gagal mengunduh incoming.json: " + (e?.message || e));
      }
      return;
    }

    if (act === "proof") {
      els.modalTitle.textContent = "Bukti Transfer";
      els.modalBody.innerHTML = (tx && tx.proof_url) ? `
        <div class="flex flex-col items-center gap-3">
          <img id="proofImg" src="${esc(tx.proof_url)}" alt="Bukti"
              class="max-h-[70vh] w-auto rounded-lg" loading="lazy" crossorigin="anonymous">
          <div class="flex items-center gap-2">
            <button id="btnDownloadProof"
                    class="px-3 py-2 rounded-md border border-slate-300 dark:border-[#3f3f46]"
                    data-tx-id="${esc(tx.id)}">
              Unduh JPG
            </button>
          </div>
        </div>
      ` : `<div class="text-slate-500">Bukti transfer belum tersedia.</div>`;
      openModal();

      const btnDownload = document.getElementById("btnDownloadProof");
      if (btnDownload) {
        btnDownload.addEventListener("click", () => {
          const txId = btnDownload.dataset.txId || "proof";
          downloadProofAsJpeg(txId);
        });
      }

      return;
    }

    if (act==="log"){
      els.modalTitle.textContent = "LOG Gagal";
      const hasAny = tx && (tx.fail_log_url || tx.fail_reason);
      els.modalBody.innerHTML = hasAny ? `
        <div class="space-y-2">
          ${tx.fail_reason ? `<div><span class="font-semibold">Reason:</span> ${esc(tx.fail_reason)}</div>` : ``}
          ${tx.fail_log_url ? `<div><a href="${esc(tx.fail_log_url)}" target="_blank" rel="noopener" class="text-blue-600 hover:underline">Buka LOG</a></div>` : ``}
        </div>
      ` : `<div class="text-slate-500">LOG belum tersedia dari device.</div>`;
      openModal();
      return;
    }
  }

  function handleTbodyClick(e){
    const btn = e.target.closest('button[data-act]');
    if (!btn || !els.tbody.contains(btn)) return;

    // hard guard: jika tombol sedang busy/disabled, abaikan klik
    if (btn.disabled || btn.getAttribute('aria-disabled') === 'true' || btn.dataset.busy === '1') {
      e.preventDefault();
      e.stopPropagation();
      return;
    }

    const act = btn.getAttribute('data-act');
    const id  = btn.getAttribute('data-id');

    // ekstra proteksi: saat act=auto, langsung set busy di elemen yg diklik
    if (act === 'auto') {
      btn.dataset.busy = '1';
      btn.setAttribute('disabled','');
      btn.classList.add('is-disabled');
    }
    if (!act || !id) return;
    handleAction(act, id);
  }

  function confirmModal(type, message, onYes){
    const isApprove = type === "approve";

    // sembunyikan header modal khusus konfirmasi
    const headerWrap = els.modalTitle?.parentElement;
    headerWrap?.classList.add("hidden");
    els.modalClose?.classList.add("hidden");
    els.modalTitle.textContent = "";

    const circleCls = isApprove
      ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300"
      : "bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-300";

    const iconSVG = isApprove
      ? `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9 12l2 2 4-4"/></svg>`
      : `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`;

    setModalWidth("compact");
    els.modalBody.innerHTML = `
      <div class="px-6 pt-6 pb-4 text-center">
        <div class="mx-auto mb-3 w-14 h-14 rounded-full flex items-center justify-center ${circleCls}">
          ${iconSVG}
        </div>
        <div class="text-[15px] font-medium">${esc(message)}</div>
      </div>
      <div class="px-6 pb-6 flex items-center justify-center gap-2">
        <button id="btnYes" class="px-3 py-1.5 rounded-md bg-blue-600 hover:bg-blue-700 text-white font-semibold">Iya</button>
        <button id="btnCancel" class="px-3 py-1.5 rounded-md border border-slate-300 dark:border-[#3f3f46] bg-slate-100 dark:bg-[#2a2a2a] hover:bg-slate-200 dark:hover:bg-[#3f3f46]">Tidak</button>
      </div>
    `;
    openModal();

    const yesBtn = document.getElementById("btnYes");
    const cancelBtn = document.getElementById("btnCancel");

    const onKey = (ev) => {
      if (ev.key === "Enter" || ev.code === "Space") { ev.preventDefault(); yesBtn?.click(); }
      if (ev.key === "Escape") { ev.preventDefault(); cancelBtn?.click(); }
    };
    document.addEventListener("keydown", onKey, { passive:false });

    const cleanup = () => {
      document.removeEventListener("keydown", onKey);
      headerWrap?.classList.remove("hidden");
      els.modalClose?.classList.remove("hidden");
    };

    cancelBtn.onclick = () => { cleanup(); closeModal(); };
    yesBtn.onclick = async () => {
      cleanup(); closeModal();
      try { await onYes(); } catch(e){ alert("Error: " + (e?.message||e)); }
    };
  }

  function editModal(cur){
    els.modalTitle.textContent = "Edit Withdraw";
    els.modalBody.innerHTML = `
      <form id="editForm" class="space-y-4 text-left">
        <div class="grid sm:grid-cols-2 gap-3">
          <label class="block">
            <span class="text-[12px] font-semibold text-slate-700 dark:text-slate-300">Bank Tujuan</span>
            <input name="bankDst" value="${esc(cur.bank_dst||'')}"
              class="mt-1 w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-[#3f3f46]
                    bg-white dark:bg-[#0f0f0f] text-[13px]
                    focus:outline-none focus:ring-2 focus:ring-blue-500/60 focus:border-blue-500"/>
          </label>

          <label class="block">
            <span class="text-[12px] font-semibold text-slate-700 dark:text-slate-300">Layanan</span>
            <select name="layanan"
              class="mt-1 w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-[#3f3f46]
                    bg-white dark:bg-[#0f0f0f] text-[13px]
                    focus:outline-none focus:ring-2 focus:ring-blue-500/60 focus:border-blue-500">
              <option value="bifast" ${cur.layanan==='bifast'?'selected':''}>BI FAST</option>
              <option value="realtime" ${cur.layanan==='realtime'?'selected':''}>REALTIME</option>
            </select>
          </label>

          <label class="block sm:col-span-2">
            <span class="text-[12px] font-semibold text-slate-700 dark:text-slate-300">Nama Tujuan</span>
            <input name="nameDst" value="${esc(cur.name_dst||'')}"
              class="mt-1 w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-[#3f3f46]
                    bg-white dark:bg-[#0f0f0f] text-[13px]
                    focus:outline-none focus:ring-2 focus:ring-blue-500/60 focus:border-blue-500"/>
          </label>

          <label class="block">
            <span class="text-[12px] font-semibold text-slate-700 dark:text-slate-300">No. Tujuan</span>
            <input name="noDst" value="${esc(cur.no_dst||'')}"
              class="mt-1 w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-[#3f3f46]
                    bg-white dark:bg-[#0f0f0f] text-[13px]
                    focus:outline-none focus:ring-2 focus:ring-blue-500/60 focus:border-blue-500"/>
          </label>

          <label class="block">
            <span class="text-[12px] font-semibold text-slate-700 dark:text-slate-300">Jumlah WD</span>
            <input name="amount" value="${esc(cur.amount||0)}" inputmode="numeric"
              class="mt-1 w-full px-3 py-2 rounded-lg border border-slate-300 dark:border-[#3f3f46]
                    bg-white dark:bg-[#0f0f0f] text-[13px]
                    focus:outline-none focus:ring-2 focus:ring-blue-500/60 focus:border-blue-500"/>
          </label>
        </div>

        <div class="flex items-center gap-2 justify-end pt-2">
          <button type="button" id="editCancel"
            class="px-3 py-2 rounded-md border border-slate-300 dark:border-[#3f3f46]
                  hover:bg-slate-100 dark:hover:bg-[#2a2a2a]">Batal</button>
          <button type="submit"
            class="px-3 py-2 rounded-md bg-blue-600 hover:bg-blue-700 text-white">Simpan</button>
        </div>
      </form>`;
    openModal();

    document.getElementById("editCancel").onclick = closeModal;
    document.getElementById("editForm").onsubmit = async (e)=>{
      e.preventDefault();
      const fd = new FormData(e.target);
      const payload = {
        bankDst: fd.get("bankDst"),
        nameDst: fd.get("nameDst"),
        noDst:   fd.get("noDst"),
        amount:  fd.get("amount"),
        layanan: fd.get("layanan"),
      };
      await apiFetchAsUser(`/api/awd/tx/${encodeURIComponent(cur.id)}`, { method:"PUT", json: payload });
      closeModal();
      await fetchAndRender();
    };
  }

  // ===== BUKTI TRANSFER: hanya Unduh JPG =====
  async function _blobToJpegBlob(srcBlob, quality=0.92){
    if ('createImageBitmap' in window) {
      const bmp = await createImageBitmap(srcBlob);
      const canvas = document.createElement("canvas");
      canvas.width = bmp.width; canvas.height = bmp.height;
      const ctx = canvas.getContext("2d");
      ctx.drawImage(bmp, 0, 0);
      return new Promise((resolve, reject) => {
        canvas.toBlob(b => b ? resolve(b) : reject(new Error("Konversi ke JPG gagal")), "image/jpeg", quality);
      });
    }
    return new Promise((resolve, reject)=>{
      const img = new Image();
      img.onload = ()=>{
        const canvas = document.createElement("canvas");
        canvas.width = img.naturalWidth; canvas.height = img.naturalHeight;
        const ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0);
        canvas.toBlob(b => b ? resolve(b) : reject(new Error("Konversi ke JPG gagal")), "image/jpeg", quality);
        URL.revokeObjectURL(img.src);
      };
      img.onerror = ()=> reject(new Error("Gagal decode gambar."));
      const url = URL.createObjectURL(srcBlob);
      img.src = url;
    });
  }

  async function _getProofJpegBlob(quality = 0.92) {
    const img = document.getElementById("proofImg");
    if (!img) throw new Error("Gambar bukti belum ada.");
    const src = img.currentSrc || img.src;
    const srcUrl = new URL(src, location.href);
    const sameOrigin = srcUrl.origin === location.origin;

    try {
      const resp = await fetch(src, {
        mode: sameOrigin ? "same-origin" : "cors",
        credentials: sameOrigin ? "include" : "omit",
        cache: "no-store",
        referrerPolicy: "no-referrer"
      });
      if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
      const srcBlob = await resp.blob();
      return _blobToJpegBlob(srcBlob, quality);
    } catch {
      // fallback pakai <img> langsung (butuh CORS benar agar canvas tidak tainted)
      await (img.decode?.() || Promise.resolve());
      const w = img.naturalWidth || img.width;
      const h = img.naturalHeight || img.height;
      if (!w || !h) throw new Error("invalid_size");
      const canvas = document.createElement("canvas");
      canvas.width = w; canvas.height = h;
      const ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0);
      return new Promise((resolve, reject) =>
        canvas.toBlob(b => b ? resolve(b) : reject(new Error("toBlob_failed")), "image/jpeg", quality)
      );
    }
  }

  async function downloadProofAsJpeg(txId = "proof") {
    try {
      const blob = await _getProofJpegBlob(0.92);
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `bukti-${txId}.jpg`;
      document.body.appendChild(a); a.click(); a.remove();
      URL.revokeObjectURL(url);
    } catch (e) {
      console.error(e);
      showToast("❌ Gagal mengunduh JPG");
    }
  }

  // expose global untuk onclick di HTML:
  window.downloadProofAsJpeg = downloadProofAsJpeg;

  // ===== MODAL =====
  function resetModalHeader() {
    const headerWrap = els.modalTitle?.parentElement;
    headerWrap?.classList.remove("hidden");
    els.modalClose?.classList.remove("hidden");
  }

  function openModal() {
    els.modal.classList.remove("hidden");
    els.modal.classList.add("flex");
  }

  function closeModal() {
    // setiap modal ditutup, pastikan header & tombol X balik normal
    resetModalHeader();
    els.modal.classList.add("hidden");
    els.modal.classList.remove("flex");
    setModalWidth("wide");
  }

  els.modalClose.addEventListener("click", closeModal);

  // klik overlay juga harus balikin header + tombol X
  document.getElementById("modalOverlay")?.addEventListener(
    "click",
    () => {
      closeModal();
    },
    { passive: true }
  );

  function enhanceDateWrap(wrap){
    const input = wrap.querySelector('input[type="date"]');
    if(!input) return;
    wrap.tabIndex = 0;
    wrap.classList.add('cursor-pointer');

    const openPicker = () => {
      input.focus();
      if (typeof input.showPicker === 'function') {
        try { input.showPicker(); } catch(_) {}
      }
    };
    wrap.addEventListener('click', (e) => { e.preventDefault(); openPicker(); });
    wrap.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.code === 'Space') { e.preventDefault(); openPicker(); }
    });
    input.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.code === 'Space') { e.preventDefault(); openPicker(); }
    });
  }
  function setupDatePickers(){
    document.querySelectorAll('.date-field').forEach(enhanceDateWrap);
  }

  // ===== LOAD PROFILE (username_op_map) =====
  async function loadProfileWD(){
    try{
      const resp = await apiFetch(`/api/awd/profile-sheets?brand=${activeBrand}`);
      const prof = resp?.profile || {};
      PROFILE_WD.username_op_map = prof.username_op_map || {};
      PROFILE_WD.op_default_case = prof.op_default_case || "lower";
      OP_MAP_LOWER = {};
      for (const k of Object.keys(PROFILE_WD.username_op_map)) {
        OP_MAP_LOWER[String(k).toLowerCase()] = PROFILE_WD.username_op_map[k];
      }
    }catch(e){
      PROFILE_WD.username_op_map = {};
      PROFILE_WD.op_default_case = "lower";
      OP_MAP_LOWER = {};
    }
  }

  // ===== Panel register + heartbeat (dipakai worker & legacy) =====
  let PANEL_ID = null;
  const PANEL_HEARTBEAT_MS = 15000;

  async function registerPanelAndStartHeartbeat(){
    try{
      const stored = localStorage.getItem("panel_id") || "";
      const reg = await apiFetchAsUser("/api/awd/panel/register", {
        method: "POST",
        json: { brand: activeBrand, panelId: stored || undefined }
      });
      PANEL_ID = reg?.panelId || stored || null;
      if (PANEL_ID) localStorage.setItem("panel_id", PANEL_ID);

      (async function beat(){
        try{
          if (PANEL_ID){
            await apiFetchAsUser("/api/awd/panel/heartbeat", {
              method:"POST",
              json:{ brand: activeBrand, panelId: PANEL_ID }
            });
          }
        }catch(_){}
        setTimeout(beat, PANEL_HEARTBEAT_MS);
      })();
    }catch(e){
      console.warn("[panel] register/heartbeat gagal:", e?.message||e);
    }
  }

  // akun online per device untuk hint pemilihan device otomatis (disiapkan, belum dipakai)
  let ACC_BY_DEVICE = new Map();
  async function loadAccountsOnline(){
    const rid = ++REQ.accounts;
    try{
      const r = await apiFetch(`/api/awd/accounts?brand=${activeBrand}&status=online`);
      if (rid !== REQ.accounts) return;
      const arr = (r && r.accounts) || [];
      const map = new Map();
      for(const a of arr){
        const dev = String(a.deviceId || "").trim();
        if(!dev) continue;
        const tag = `${a.bank} ${a.name}`;
        if(!map.has(dev)) map.set(dev, []);
        map.get(dev).push(tag);
      }
      ACC_BY_DEVICE = map;
    }catch(_){ /* diamkan */ }
  }

  async function fetchAndRender(){
    if (FIRST_LOAD) renderSkeletonRows(8);
    const rid = ++REQ.tx;
    const qs = buildQueryFromState();
    const resp = await apiFetch(`/api/awd/tx?${qs}`).catch(()=>null);
    if (rid !== REQ.tx || !resp) return;

    const list = (resp?.rows || []).map(t => updateTxDerived({...t}));
    list.sort((a, b) =>
      (b.created_at || 0) - (a.created_at || 0) ||
      (b.occurred_at || 0) - (a.occurred_at || 0)
    );

    // Kalau status di server sekarang "Failed" dan di local ada data lock,
    // SELALU buka kunci supaya tombol "Auto" bisa dipakai ulang
    // (percobaan ke-1, ke-2, ke-3, dst).
    list.forEach(tx => {
      const ui = mapStatusBackendToUI(tx.status);
      const meta = getLockMeta(tx.id);

      if (ui === "Failed" && meta) {
        unlockAuto(tx.id);
      }
    });


    VIEW_TX = list;
    CURRENT_TOTAL = Number(resp?.total || 0);
    PAGE = Number(resp?.page || PAGE);
    CURRENT_PAGES = Number(resp?.pages || 1);

    renderTablePage();
    FIRST_LOAD = false;
  }

  // ===== BOOT (SATU KALI) =====
  (async function boot(){
    initAwdWorker(); // siapkan worker (walau Auto Proses tidak memakai worker)

    renderSkeletonRows(8);

    const session = await requireSession({ scope: 'autowd' });
    SESSION = session;
    const isSuperadmin = (session?.role === "superadmin");
    const scopes = normalizeScopes(session?.scopes ?? session?.permissions ?? session?.perms ?? session?.scope_map);

    applySidebar(isSuperadmin, scopes);
    applyStatic(session);
    setupDatePickers();
    await registerPanelAndStartHeartbeat();

    if (!hasScope(REQUIRED_SCOPE_PAGE, { isSuperadmin, scopes })) {
      els.forbiddenOverlay.classList.remove("hidden");
      return;
    }

    await loadProfileWD();
    await loadAccountsOnline();

    await loadDistinctOptions();

    // pakai filter tersimpan (kalau ada)
    loadFilterState();

    await fetchAndRender();

    // search & filter events (debounced)
    const triggerFetchDebounced = debounce(()=>{ PAGE = 1; fetchAndRender(); }, 200);

    // Search box
    els.q.addEventListener("input", ()=>{
      STATE.q = (els.q.value || "").trim();
      saveFilterState();
      triggerFetchDebounced();
    });

    // Filter dropdown (semua field)
    ["change","input"].forEach(ev=>{
      els.fBankSrc.addEventListener(ev, ()=>{
        STATE.bankSrc = els.fBankSrc.value || "";
        saveFilterState();
        PAGE = 1;
        triggerFetchDebounced();
      });
      els.fNamaSrc.addEventListener(ev, ()=>{
        STATE.nameSrc = els.fNamaSrc.value || "";
        saveFilterState();
        PAGE = 1;
        triggerFetchDebounced();
      });
      els.fBankDst.addEventListener(ev, ()=>{
        STATE.bankDst = els.fBankDst.value || "";
        saveFilterState();
        PAGE = 1;
        triggerFetchDebounced();
      });
      els.fOP.addEventListener(ev, ()=>{
        STATE.op = els.fOP.value || "";
        saveFilterState();
        PAGE = 1;
        triggerFetchDebounced();
      });
      els.fStatus.addEventListener(ev, ()=>{
        STATE.statusUI = els.fStatus.value || "";
        saveFilterState();
        PAGE = 1;
        triggerFetchDebounced();
      });
      els.fDateStart.addEventListener(ev, ()=>{
        STATE.dateStart = els.fDateStart.value || "";
        saveFilterState();
        PAGE = 1;
        triggerFetchDebounced();
      });
      els.fDateEnd.addEventListener(ev, ()=>{
        STATE.dateEnd = els.fDateEnd.value || "";
        saveFilterState();
        PAGE = 1;
        triggerFetchDebounced();
      });
    });

    // Reset filter = kosongkan STATE + hapus dari localStorage
    els.resetFilter.addEventListener("click", ()=>{
      els.q.value = "";
      els.fDateStart.value = ""; els.fDateEnd.value = "";
      els.fBankSrc.value = "";  els.fNamaSrc.value = ""; els.fBankDst.value = "";
      els.fOP.value = "";       els.fStatus.value = "";

      STATE.q = "";
      STATE.dateStart = "";
      STATE.dateEnd = "";
      STATE.bankSrc = "";
      STATE.nameSrc = "";
      STATE.bankDst = "";
      STATE.op = "";
      STATE.statusUI = "";

      clearFilterState();

      PAGE = 1;
      fetchAndRender();
      document.querySelectorAll(".date-field").forEach(w=>w.classList.remove("has-value"));
    });

    // Terapkan = tutup dropdown, pakai STATE yg sudah diset + simpan lagi (jaga2)
    els.applyFilter.addEventListener("click", ()=>{
      els.filterDropdown.classList.add("hidden");
      saveFilterState();
      PAGE = 1;
      fetchAndRender();
    });

    // delegated actions
    els.tbody.addEventListener('click', handleTbodyClick);
  })();

  // ===== BFCache / Back-Forward anti-stale =====
  window.addEventListener("pageshow", (evt) => {
    const nav = performance.getEntriesByType?.("navigation")?.[0];
    const isBF = evt.persisted || (nav && nav.type === "back_forward");
    if (isBF) { fetchAndRender(); }
  });

  // ===== Logout =====
  els.logoutBtn?.addEventListener("click", async ()=>{
    try { await apiFetch("/api/logout", { method:"POST" }); } catch(_) {}
    window.location = "login.html";
  });
