<?php
// ============================================================
// OPTIMIZED index.php — Escobar Billing
// v3.2 — All bugs fixed + expired restore on page load
// ============================================================

include 'config.php';
checkLogin();
$page_title  = "Command Center";
$active_page = "dashboard";

$flash_success = '';
if (!empty($_SESSION['flash_success'])) {
    $flash_success = $_SESSION['flash_success'];
    unset($_SESSION['flash_success']);
}
include 'layout.php';

// ── Cache ADB list di session, max 30 detik ─────────────────
if (!isset($_SESSION['_adb_t']) || (time() - $_SESSION['_adb_t']) > 30) {
    $_SESSION['_adb_cache'] = serialize(getADBList());
    $_SESSION['_adb_t']     = time();
}
$adb_list = unserialize($_SESSION['_adb_cache'] ?? 'a:0:{}') ?: [];

// ── Settings ─────────────────────────────────────────────────
$default_rate = get_setting($conn, 'default_open_bill_rate') ?: "10000";
$min_charge   = get_setting($conn, 'min_open_bill')          ?: "3000";
$round_val    = get_setting($conn, 'round_open_bill')        ?: "500";

// ── Semua TV + transaksi aktif + member = 1 query JOIN ───────
$tvs_result = $conn->query("
    SELECT t.*,
           trx.jenis_paket, trx.start_time, trx.target_selesai,
           trx.remaining_seconds, trx.rate_per_jam,
           m.nama_member
    FROM tvs t
    LEFT JOIN transactions trx ON t.current_trx_id = trx.id
    LEFT JOIN members m        ON trx.member_id = m.id
    ORDER BY t.nama_tv ASC
");

// ── Packages & options ───────────────────────────────────────
$pkgs = $conn->query("SELECT * FROM packages ORDER BY durasi_menit ASC");
$pkg_options = "";
while ($p = $pkgs->fetch_assoc()) {
    $pkg_options .= "<option value='" . htmlspecialchars($p['id']) . "'>"
        . htmlspecialchars($p['nama_paket'])
        . " (Rp " . number_format($p['harga']) . ")</option>";
}

// ── Members ──────────────────────────────────────────────────
$members_q = $conn->query("SELECT id, nama_member, saldo FROM members ORDER BY nama_member ASC");
$member_options = "<option value=''>-- Tamu Regular (Cash) --</option>";
while ($m = $members_q->fetch_assoc()) {
    $member_options .= "<option value='" . htmlspecialchars($m['id']) . "'>"
        . htmlspecialchars($m['nama_member'])
        . " [Rp " . number_format($m['saldo']) . "]</option>";
}

// ── Sticks ───────────────────────────────────────────────────
$sticks_data  = [];
$sticks_query = $conn->query("SELECT * FROM sticks WHERE status = 'available'");
while ($stk = $sticks_query->fetch_assoc()) $sticks_data[] = $stk;

// ── Available TVs (untuk modal pindah) ───────────────────────
$available_tvs  = [];
$avail_tv_query = $conn->query("SELECT id, nama_tv FROM tvs WHERE status = 'available' ORDER BY nama_tv ASC");
while ($avt = $avail_tv_query->fetch_assoc()) $available_tvs[] = $avt;

// ── Tuya plug map ─────────────────────────────────────────────
$_tuya_plug_map = [];
$_tbl_tuya = $conn->query("SHOW TABLES LIKE 'tuya_devices'");
if ($_tbl_tuya && $_tbl_tuya->num_rows > 0) {
    $sp_all = $conn->query("SELECT id, tv_id FROM tuya_devices WHERE tv_id IS NOT NULL");
    if ($sp_all) {
        while ($sp_row = $sp_all->fetch_assoc())
            $_tuya_plug_map[(int)$sp_row['tv_id']][] = (int)$sp_row['id'];
    }
}

// ── Preload FnB untuk semua trx aktif = 1 query ─────────────
$tvs_arr    = [];
$active_ids = [];
while ($row = $tvs_result->fetch_assoc()) {
    $tvs_arr[] = $row;
    if (!empty($row['current_trx_id'])) $active_ids[] = (int)$row['current_trx_id'];
}

$_fnb_map = [];
if (!empty($active_ids)) {
    $ids_str = implode(',', $active_ids);
    $fnb_q   = $conn->query("SELECT trx_id, SUM(total_harga) AS total FROM orders_fnb WHERE trx_id IN ($ids_str) GROUP BY trx_id");
    if ($fnb_q) {
        while ($fr = $fnb_q->fetch_assoc())
            $_fnb_map[(int)$fr['trx_id']] = (int)$fr['total'];
    }
}

// ── Packages untuk modal Add Time ────────────────────────────
$pkgs2 = $conn->query("SELECT * FROM packages ORDER BY durasi_menit ASC");
$pkg_addtime_options = "";
while ($p2 = $pkgs2->fetch_assoc())
    $pkg_addtime_options .= "<option value='" . htmlspecialchars($p2['id'])
        . "' data-menit='" . (int)$p2['durasi_menit']
        . "' data-harga='" . (int)$p2['harga'] . "'>"
        . htmlspecialchars($p2['nama_paket'])
        . " (+" . htmlspecialchars($p2['durasi_menit']) . " menit)"
        . " - Rp " . number_format($p2['harga']) . "</option>";

// ── Snacks (modal FnB) ────────────────────────────────────────
$snack_options = "";
$snack_q = $conn->query("SELECT * FROM snacks WHERE stok > 0 ORDER BY nama_barang ASC");
if ($snack_q && $snack_q->num_rows > 0) {
    while ($s = $snack_q->fetch_assoc())
        $snack_options .= "<option value='" . htmlspecialchars($s['id'])
            . "' data-harga='" . (int)$s['harga_jual'] . "'>"
            . htmlspecialchars($s['nama_barang'])
            . " (Rp " . number_format($s['harga_jual']) . ")"
            . " - Stok: " . $s['stok'] . "</option>";
} else {
    $snack_options = "<option value=''>Tidak ada snack tersedia</option>";
}

// ── [FIX] Cek transaksi expired yang belum dikonfirmasi ──────
// Digunakan untuk restore state saat halaman di-load ulang
$expired_pending = [];
foreach ($tvs_arr as $tv) {
    if ($tv['status'] === 'available' || empty($tv['current_trx_id'])) continue;
    if (($tv['jenis_paket'] ?? 'fixed') !== 'fixed') continue;
    if (empty($tv['target_selesai'])) continue;

    $target_ts = strtotime($tv['target_selesai']);
    if ($target_ts > 0 && $target_ts < time()) {
        $expired_pending[] = [
            'tv_id'   => (int)$tv['id'],
            'trx_id'  => (int)$tv['current_trx_id'],
            'nama_tv' => $tv['nama_tv'],
        ];
    }
}
?>

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Plus+Jakarta+Sans:wght@400;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Plus+Jakarta+Sans:wght@400;600;700;800&family=JetBrains+Mono:wght@400;700&display=swap"></noscript>
<link rel="preload" as="style" href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css"></noscript>

<style>
    :root {
        --bg-dark: #030712;
        --bg-gradient: linear-gradient(135deg, #030712 0%, #0c1929 50%, #0a192f 100%);
        --card-bg: rgba(15, 23, 42, 0.6);
        --card-border: rgba(6, 182, 212, 0.15);
        --primary: #06b6d4;
        --primary-light: #22d3ee;
        --primary-dark: #0891b2;
        --primary-glow: rgba(6, 182, 212, 0.6);
        --success: #10b981;
        --success-glow: rgba(16, 185, 129, 0.5);
        --warning: #f59e0b;
        --warning-glow: rgba(245, 158, 11, 0.5);
        --danger: #ef4444;
        --danger-glow: rgba(239, 68, 68, 0.5);
        --text-main: #f1f5f9;
        --text-dim: #64748b;
        --glass-bg: rgba(255, 255, 255, 0.03);
        --glass-border: rgba(255, 255, 255, 0.08);
    }

    * { box-sizing: border-box; }
    body { font-family: 'Plus Jakarta Sans', system-ui, sans-serif; background: var(--bg-dark); color: var(--text-main); min-height: 100vh; position: relative; overflow-x: hidden; }

    .animated-bg { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; overflow: hidden; background: var(--bg-gradient); pointer-events: none; contain: strict; }
    .floating-orb { position: absolute; border-radius: 50%; filter: blur(80px); opacity: 0.35; animation: floatOrb 20s ease-in-out infinite; pointer-events: none; will-change: transform; }
    .orb-1 { width: 500px; height: 500px; background: radial-gradient(circle, var(--primary) 0%, transparent 70%); top: -150px; right: -150px; }
    .orb-2 { width: 400px; height: 400px; background: radial-gradient(circle, #8b5cf6 0%, transparent 70%); bottom: -100px; left: -100px; animation-delay: -7s; }
    @keyframes floatOrb { 0%, 100% { transform: translate(0,0) scale(1); } 33% { transform: translate(40px,-40px) scale(1.05); } 66% { transform: translate(-30px,25px) scale(0.97); } }

    .grid-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%;
        background-image: linear-gradient(rgba(6,182,212,0.03) 1px, transparent 1px),
                          linear-gradient(90deg, rgba(6,182,212,0.03) 1px, transparent 1px);
        background-size: 50px 50px; pointer-events: none; }

    .particles { position: absolute; width: 100%; height: 100%; pointer-events: none; }
    .particle { position: absolute; width: 3px; height: 3px; background: var(--primary); border-radius: 50%; opacity: 0; animation: particleFloat 10s ease-in-out infinite; pointer-events: none; will-change: transform, opacity; }
    @keyframes particleFloat { 0% { opacity: 0; transform: translateY(100vh); } 10% { opacity: 0.6; } 90% { opacity: 0.6; } 100% { opacity: 0; transform: translateY(-20px); } }

    .grid-units { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 25px; padding: 20px 30px 30px; position: relative; z-index: 1; }

    .unit-card { background: var(--card-bg); border: 1px solid var(--card-border); border-radius: 24px; padding: 25px; position: relative; overflow: hidden; transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; contain: layout style; }
    .unit-card:hover { backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); transform: translateY(-8px) scale(1.01); box-shadow: 0 20px 40px -10px rgba(0,0,0,0.5), 0 0 50px -15px var(--primary-glow); }
    .unit-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 24px; padding: 2px; background: linear-gradient(135deg, transparent, transparent); -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor; mask-composite: exclude; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; }
    .unit-card:hover::before { background: linear-gradient(135deg, var(--primary), var(--primary-light), #8b5cf6); opacity: 1; }
    .unit-card.active { border-color: rgba(16,185,129,0.3); }
    .unit-card.active::after { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, var(--success), var(--primary)); border-radius: 24px 24px 0 0; pointer-events: none; }
    .unit-card.paused { border-color: rgba(245,158,11,0.3); }
    .unit-card.paused::after { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, var(--warning), #fbbf24); border-radius: 24px 24px 0 0; pointer-events: none; }

    .card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; }
    .tv-info { display: flex; align-items: center; gap: 12px; }
    .tv-icon { width: 48px; height: 48px; background: linear-gradient(135deg, var(--primary), var(--primary-dark)); border-radius: 14px; display: flex; align-items: center; justify-content: center; font-size: 1.4rem; color: #fff; box-shadow: 0 8px 20px -8px var(--primary-glow); }
    .tv-title { font-family: 'Orbitron', sans-serif; font-size: 1.2rem; font-weight: 700; letter-spacing: 1px; margin-bottom: 4px; }

    .adb-status { font-size: 0.65rem; font-weight: 700; display: flex; align-items: center; gap: 5px; }
    .adb-status.online { color: var(--success); }
    .adb-status.offline { color: var(--danger); }
    .adb-dot { width: 8px; height: 8px; border-radius: 50%; animation: dotPulse 2s ease-in-out infinite; will-change: transform; }
    .adb-status.online .adb-dot { background: var(--success); box-shadow: 0 0 8px var(--success-glow); }
    .adb-status.offline .adb-dot { background: var(--danger); box-shadow: 0 0 8px var(--danger-glow); }
    @keyframes dotPulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.3); opacity: 0.7; } }

    .status-pill { padding: 6px 14px; border-radius: 20px; font-size: 0.65rem; font-weight: 800; text-transform: uppercase; letter-spacing: 1px; display: flex; align-items: center; gap: 6px; }
    .unit-card.active .status-pill { color: var(--success); background: rgba(16,185,129,0.15); border: 1px solid rgba(16,185,129,0.3); box-shadow: 0 0 20px -5px var(--success-glow); }
    .unit-card.paused .status-pill { color: var(--warning); background: rgba(245,158,11,0.15); border: 1px solid rgba(245,158,11,0.3); box-shadow: 0 0 20px -5px var(--warning-glow); }
    .unit-card.available .status-pill { color: var(--text-dim); background: var(--glass-bg); border: 1px solid var(--glass-border); }
    .status-pill i { font-size: 0.8rem; }

    .timer-box { background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.2) 100%); border: 1px solid var(--glass-border); border-radius: 20px; padding: 30px 20px; text-align: center; margin-bottom: 20px; position: relative; overflow: hidden; }
    .timer-box::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: conic-gradient(from 0deg, transparent 0deg, var(--primary-glow) 60deg, transparent 120deg); animation: timerRing 4s linear infinite; opacity: 0.25; pointer-events: none; will-change: transform; }
    .unit-card.active .timer-box::before { background: conic-gradient(from 0deg, transparent 0deg, var(--success-glow) 60deg, transparent 120deg); }
    .unit-card.paused .timer-box::before { animation-play-state: paused; background: conic-gradient(from 0deg, transparent 0deg, var(--warning-glow) 60deg, transparent 120deg); }
    @keyframes timerRing { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
    .timer-box-inner { position: relative; z-index: 1; }

    .timer-digits { font-family: 'JetBrains Mono', monospace; font-size: 3.2rem; font-weight: 700; color: #fff; text-shadow: 0 0 20px var(--primary-glow), 0 0 40px var(--primary-glow); letter-spacing: 4px; display: block; transition: color 0.3s ease; }
    .unit-card.active .timer-digits { text-shadow: 0 0 20px var(--success-glow), 0 0 40px var(--success-glow); }
    .unit-card.paused .timer-digits { text-shadow: 0 0 20px var(--warning-glow), 0 0 40px var(--warning-glow); }

    .billing-text { font-size: 0.75rem; color: var(--text-dim); font-weight: 700; margin-top: 12px; display: block; text-transform: uppercase; letter-spacing: 2px; }
    .billing-amount { color: var(--success); font-family: 'JetBrains Mono', monospace; font-size: 1rem; text-shadow: 0 0 10px var(--success-glow); }
    .time-expired { color: var(--danger) !important; animation: expiredPulse 1s ease-in-out infinite; text-shadow: 0 0 20px var(--danger-glow), 0 0 40px var(--danger-glow) !important; }
    @keyframes expiredPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }

    .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 20px; }
    .info-item { background: var(--glass-bg); padding: 14px; border-radius: 16px; border: 1px solid var(--glass-border); text-align: center; transition: background 0.3s ease, border-color 0.3s ease, transform 0.3s ease; }
    .info-item:hover { background: rgba(6,182,212,0.1); border-color: var(--primary); transform: translateY(-2px); }
    .info-label { font-size: 0.6rem; color: var(--text-dim); text-transform: uppercase; font-weight: 800; margin-bottom: 6px; display: block; letter-spacing: 1px; }
    .info-value { font-size: 0.9rem; font-weight: 700; color: #fff; display: flex; align-items: center; justify-content: center; gap: 6px; }
    .info-value i { color: var(--primary); }

    .tab-container { display: flex; gap: 8px; margin-bottom: 15px; background: rgba(0,0,0,0.3); padding: 6px; border-radius: 14px; position: relative; z-index: 5; }
    .tab-btn { flex: 1; background: transparent; border: none; color: var(--text-dim); padding: 10px; font-weight: 800; cursor: pointer; font-size: 0.7rem; border-radius: 10px; transition: all 0.3s ease; text-transform: uppercase; letter-spacing: 1px; position: relative; z-index: 5; }
    .tab-btn.active { background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color: #000; box-shadow: 0 4px 15px -3px var(--primary-glow); }
    .tab-btn:hover:not(.active) { background: rgba(255,255,255,0.05); color: var(--text-main); }

    .stik-selector { position: relative; margin-bottom: 12px; z-index: 10; }
    .stik-trigger { background: rgba(0,0,0,0.3); border: 1px solid var(--glass-border); padding: 14px 16px; border-radius: 14px; font-size: 0.85rem; color: var(--text-dim); cursor: pointer; display: flex; align-items: center; gap: 10px; transition: all 0.3s ease; position: relative; z-index: 10; }
    .stik-trigger:hover { border-color: var(--primary); background: rgba(6,182,212,0.1); }
    .stik-trigger i { font-size: 1.2rem; color: var(--primary); }
    .stik-dropdown { position: absolute; top: calc(100% + 8px); left: 0; width: 100%; background: rgba(15,23,42,0.98); backdrop-filter: blur(20px); border: 1px solid var(--primary); border-radius: 16px; padding: 12px; z-index: 100; display: none; box-shadow: 0 20px 40px -10px rgba(0,0,0,0.5), 0 0 30px -10px var(--primary-glow); animation: dropdownSlide 0.3s ease; }
    @keyframes dropdownSlide { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
    .stik-selector.active .stik-dropdown { display: block; }
    .stik-item input { display: none; }
    .stik-item label { display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: 12px; cursor: pointer; transition: background 0.2s ease; font-weight: 600; border: 1px solid transparent; }
    .stik-item label:hover { background: rgba(6,182,212,0.1); }
    .stik-item input:checked + label { background: rgba(6,182,212,0.2); color: var(--primary); border-color: var(--primary); }
    .stik-item label i { font-size: 1.3rem; }

    .modern-select { width: 100%; background: rgba(0,0,0,0.3); border: 1px solid var(--glass-border); padding: 14px 16px; border-radius: 14px; color: #fff; margin-bottom: 12px; outline: none; font-size: 0.9rem; transition: border-color 0.3s ease, background 0.3s ease; cursor: pointer; position: relative; z-index: 5; -webkit-appearance: none; -moz-appearance: none; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2394a3b8' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 15px center; }
    .modern-select:hover, .modern-select:focus { border-color: var(--primary); background-color: rgba(6,182,212,0.1); }
    .modern-select option { background: #1e293b; color: #fff; padding: 10px; }

    .btn-start { width: 100%; padding: 16px; border-radius: 16px; border: none; background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color: #000; font-weight: 800; cursor: pointer; text-transform: uppercase; letter-spacing: 2px; font-size: 0.9rem; transition: transform 0.3s ease, box-shadow 0.3s ease; position: relative; overflow: hidden; z-index: 5; }
    .btn-start::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); transition: left 0.5s ease; pointer-events: none; }
    .btn-start:hover::before { left: 100%; }
    .btn-start:hover { transform: translateY(-2px); box-shadow: 0 10px 30px -10px var(--primary-glow); }
    .btn-start.success { background: linear-gradient(135deg, var(--success), #059669); }
    .btn-start.success:hover { box-shadow: 0 10px 30px -10px var(--success-glow); }

    .action-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; position: relative; z-index: 5; }
    .btn-act { padding: 14px; border-radius: 14px; border: 1px solid var(--glass-border); background: var(--glass-bg); color: #fff; font-weight: 700; font-size: 0.8rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; transition: all 0.3s ease; text-decoration: none; position: relative; overflow: hidden; z-index: 5; }
    .btn-act::before { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(6,182,212,0.3); border-radius: 50%; transform: translate(-50%,-50%); transition: width 0.4s ease, height 0.4s ease; pointer-events: none; }
    .btn-act:hover::before { width: 300px; height: 300px; }
    .btn-act:hover { border-color: var(--primary); transform: translateY(-3px); box-shadow: 0 10px 20px -10px var(--primary-glow); }
    .btn-act i { font-size: 1.1rem; position: relative; z-index: 1; transition: transform 0.3s ease; }
    .btn-act span { position: relative; z-index: 1; }
    .btn-act:hover i { transform: scale(1.2); }
    .btn-act.stop { background: rgba(239,68,68,0.1); border-color: rgba(239,68,68,0.3); color: var(--danger); }
    .btn-act.stop::before { background: rgba(239,68,68,0.3); }
    .btn-act.stop:hover { background: var(--danger); color: #fff; box-shadow: 0 10px 20px -10px var(--danger-glow); }
    .btn-act.addtime { background: rgba(6,182,212,0.1); border-color: rgba(6,182,212,0.3); color: var(--primary); }
    .btn-act.addtime::before { background: rgba(6,182,212,0.3); }
    .btn-act.addtime:hover { background: var(--primary); color: #000; box-shadow: 0 10px 20px -10px var(--primary-glow); }

    .plug-toggle-btn { display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.25); cursor: pointer; font-size: 0.65rem; font-weight: 700; transition: all 0.25s ease; margin-top: 3px; user-select: none; }
    .plug-toggle-btn:hover { background: rgba(255,255,255,0.08); }
    .plug-toggle-btn.plug-on  { color: var(--success); border-color: rgba(16,185,129,0.35); }
    .plug-toggle-btn.plug-off { color: var(--danger);  border-color: rgba(239,68,68,0.35); }
    .plug-toggle-btn.plug-loading { color: var(--text-dim); pointer-events: none; }

    .remote-dock { margin-top: 20px; display: flex; justify-content: center; gap: 12px; border-top: 1px solid var(--glass-border); padding-top: 20px; position: relative; z-index: 5; }
    .remote-btn { width: 46px; height: 46px; border-radius: 14px; background: var(--glass-bg); color: var(--text-dim); display: flex; align-items: center; justify-content: center; cursor: pointer; border: 1px solid var(--glass-border); transition: all 0.3s ease; font-size: 1.2rem; position: relative; z-index: 5; }
    .remote-btn.connect-btn { background: rgba(16,185,129,0.1); border-color: rgba(16,185,129,0.3); color: var(--success); }
    .remote-btn.connect-btn:hover { background: var(--success); color: #000; box-shadow: 0 0 20px var(--success-glow); }
    .remote-btn.connect-btn.connecting { animation: connectPulse 1s ease-in-out infinite; pointer-events: none; }
    .remote-btn.connect-btn.connecting i { animation: spin 1s linear infinite; }
    @keyframes connectPulse { 0%, 100% { box-shadow: 0 0 5px var(--success-glow); } 50% { box-shadow: 0 0 20px var(--success-glow); } }
    @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
    .remote-btn:hover { background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color: #000; border-color: var(--primary); transform: translateY(-3px) scale(1.1); box-shadow: 0 10px 20px -10px var(--primary-glow); }
    .remote-btn:active { transform: translateY(0) scale(0.95); }

    .modal-backdrop { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); backdrop-filter: blur(10px); display: none; align-items: center; justify-content: center; z-index: 9999; animation: modalFadeIn 0.3s ease; }
    @keyframes modalFadeIn { from { opacity: 0; } to { opacity: 1; } }
    .modal-content { background: linear-gradient(180deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%); backdrop-filter: blur(20px); border-radius: 28px; padding: 30px; border: 1px solid var(--glass-border); width: 90%; max-width: 460px; position: relative; animation: modalSlideIn 0.4s cubic-bezier(0.4,0,0.2,1); max-height: 90vh; overflow-y: auto; }
    @keyframes modalSlideIn { from { opacity: 0; transform: scale(0.9) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } }
    .modal-content h3 { font-family: 'Orbitron', sans-serif; font-size: 1.2rem; margin: 0 0 20px; display: flex; align-items: center; gap: 12px; }
    .modal-content h3 i { font-size: 1.5rem; }

    .receipt-box { background: rgba(0,0,0,0.4); padding: 20px; border-radius: 20px; margin: 15px 0; border: 1px solid var(--glass-border); }
    .billing-item { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px dashed rgba(255,255,255,0.1); font-size: 0.9rem; }
    .total-row { display: flex; justify-content: space-between; padding-top: 15px; margin-top: 15px; border-top: 2px solid var(--primary); font-weight: 800; font-size: 1.2rem; color: var(--primary); }

    .topup-quick-btn { background: rgba(6,182,212,0.1); border: 1px solid rgba(6,182,212,0.25); color: var(--primary); border-radius: 10px; padding: 8px 12px; font-size: .75rem; font-weight: 700; cursor: pointer; transition: all .25s ease; flex: 1; }
    .topup-quick-btn:hover { background: rgba(6,182,212,0.25); border-color: var(--primary); transform: translateY(-2px); }

    .pay-method-btn {
        display: flex; flex-direction: column; align-items: center; gap: 5px;
        padding: 10px 8px; background: rgba(0,0,0,.3);
        border: 1.5px solid var(--glass-border); border-radius: 12px;
        cursor: pointer; font-size: .72rem; font-weight: 700;
        color: var(--text-dim); text-align: center;
        transition: all .2s ease;
    }
    .pay-method-btn:hover { border-color: var(--primary); background: rgba(6,182,212,.08); color: var(--primary-light); }
    .pay-method-btn.selected { border-color: var(--primary) !important; background: rgba(6,182,212,.18) !important; color: var(--primary) !important; box-shadow: 0 0 12px -4px var(--primary-glow); }
    .pay-method-btn i { font-size: 1.3rem; }

    #btn_sudah_bayar { display: flex; flex-direction: column; align-items: center; gap: 6px; }
    #btn_belum_bayar { display: flex; flex-direction: column; align-items: center; gap: 6px; }
    #btn_sudah_bayar:hover { background: rgba(16,185,129,0.2) !important; border-color: var(--success) !important; box-shadow: 0 10px 25px -10px var(--success-glow); transform: translateY(-3px); }
    #btn_belum_bayar:hover { background: rgba(245,158,11,0.2) !important; border-color: var(--warning) !important; box-shadow: 0 10px 25px -10px var(--warning-glow); transform: translateY(-3px); }
    #modalBelumBayarExpired { z-index: 99998; }
    #modalBelumBayarExpired .modal-content { border-color: rgba(239,68,68,0.4); }
    #metode_pilih_grid .pay-method-btn,
    #bbexp_metode_grid .pay-method-btn { padding: 16px 8px; flex-direction: column; gap: 6px; }

    .hidden { display: none !important; }
    .no-stik-msg { color: var(--text-dim); font-size: 0.85rem; padding: 15px; text-align: center; }

    @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
    @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
    @keyframes warningPulse { 0%, 100% { box-shadow: 0 0 0 rgba(239,68,68,0); } 50% { box-shadow: 0 0 20px rgba(239,68,68,0.3); } }

    @media (max-width: 768px) {
        .grid-units { grid-template-columns: 1fr; padding: 15px; }
        .timer-digits { font-size: 2.5rem; }
    }

    ::-webkit-scrollbar { width: 8px; }
    ::-webkit-scrollbar-track { background: var(--bg-dark); }
    ::-webkit-scrollbar-thumb { background: var(--primary); border-radius: 4px; }
    ::-webkit-scrollbar-thumb:hover { background: var(--primary-light); }

    #modalStop .modal-content { position: relative; z-index: 10000; pointer-events: all !important; }
    #modalStop { z-index: 9999; pointer-events: all !important; }
    #confirm_stop_btn { position: relative; z-index: 10001; pointer-events: all !important; cursor: pointer !important; }

    /* ── Voucher styles ── */
    .voucher-input-wrap { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; position: relative; }
    .voucher-input-wrap input {
        flex: 1; background: rgba(0,0,0,0.35); border: 1.5px solid rgba(6,182,212,0.25);
        border-radius: 12px; color: #fff; padding: 11px 16px; font-size: .9rem;
        font-family: 'JetBrains Mono', monospace; font-weight: 700; letter-spacing: 1.5px;
        text-transform: uppercase; outline: none; transition: border-color .2s;
    }
    .voucher-input-wrap input::placeholder { font-family: 'Plus Jakarta Sans', sans-serif; font-weight: 400; letter-spacing: 0; text-transform: none; color: #475569; }
    .voucher-input-wrap input:focus { border-color: var(--primary); }
    .voucher-input-wrap input.v-valid { border-color: var(--success); }
    .voucher-input-wrap input.v-invalid { border-color: var(--danger); }

    .btn-cek-voucher { background: linear-gradient(135deg, var(--primary), var(--primary-dark)); color: #000; border: none; border-radius: 12px; padding: 11px 18px; font-weight: 800; font-size: .82rem; cursor: pointer; white-space: nowrap; transition: all .2s; }
    .btn-cek-voucher:hover { transform: translateY(-2px); box-shadow: 0 6px 16px -6px var(--primary-glow); }
    .btn-cek-voucher:disabled { opacity: .5; pointer-events: none; }

    .voucher-result-box { border-radius: 12px; padding: 12px 14px; font-size: .82rem; margin-bottom: 14px; display: none; align-items: center; gap: 10px; }
    .voucher-result-box.ok { background: rgba(16,185,129,.12); border: 1px solid rgba(16,185,129,.3); color: #34d399; display: flex; }
    .voucher-result-box.err { background: rgba(239,68,68,.1); border: 1px solid rgba(239,68,68,.25); color: #f87171; display: flex; }
    .voucher-result-box i { font-size: 1.3rem; flex-shrink: 0; }

    .voucher-diskon-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px dashed rgba(16,185,129,.25); color: #34d399; font-weight: 700; font-size: .9rem; }

    .voucher-clear-btn { background: none; border: none; color: #94a3b8; cursor: pointer; font-size: .75rem; text-decoration: underline; margin-left: auto; flex-shrink: 0; }
    .voucher-clear-btn:hover { color: var(--danger); }
</style>

<div class="animated-bg">
    <div class="grid-overlay"></div>

    <?php if ($flash_success): ?>
    <div id="flash-reset-banner" style="position:fixed;bottom:24px;right:24px;z-index:9999;padding:14px 20px;background:linear-gradient(135deg,rgba(16,185,129,0.95),rgba(5,150,105,0.95));border-radius:12px;color:#fff;font-weight:600;display:flex;align-items:center;gap:10px;box-shadow:0 8px 24px rgba(0,0,0,0.4);max-width:420px;">
        <i class='bx bx-check-circle' style="font-size:1.3rem;flex-shrink:0;"></i>
        <span style="font-size:0.85rem;"><?php echo htmlspecialchars($flash_success); ?></span>
        <button onclick="this.parentElement.remove()" style="margin-left:auto;background:none;border:none;color:#fff;cursor:pointer;font-size:1.2rem;flex-shrink:0;">&times;</button>
    </div>
    <script>setTimeout(()=>{const b=document.getElementById('flash-reset-banner');if(b)b.remove();},4000);</script>
    <?php endif; ?>

    <div class="floating-orb orb-1"></div>
    <div class="floating-orb orb-2"></div>
    <div class="particles" id="particles"></div>
</div>


<!-- ── STATUS SUMMARY BAR ── -->
<?php
$total_tv    = count($tvs_arr);
$tv_active   = count(array_filter($tvs_arr, fn($t) => $t['status'] === 'active'));
$tv_paused   = count(array_filter($tvs_arr, fn($t) => $t['status'] === 'paused'));
$tv_available = count(array_filter($tvs_arr, fn($t) => $t['status'] === 'available'));
?>
<div style="display:flex;gap:12px;flex-wrap:nowrap;overflow-x:auto;padding:0 30px 16px;position:relative;z-index:1;scrollbar-width:none;">
    <div style="display:flex;align-items:center;gap:10px;background:rgba(16,185,129,0.1);border:1px solid rgba(16,185,129,0.25);border-radius:14px;padding:10px 18px;flex-shrink:0;">
        <span style="width:10px;height:10px;border-radius:50%;background:var(--success);box-shadow:0 0 8px var(--success);display:inline-block;animation:dotPulse 2s infinite;"></span>
        <span style="font-size:0.8rem;font-weight:800;color:var(--success);">AKTIF</span>
        <span id="bar-aktif" style="font-size:1.3rem;font-weight:900;color:#fff;font-family:'JetBrains Mono',monospace;"><?= $tv_active ?></span>
        <span style="font-size:0.7rem;color:#64748b;">unit</span>
    </div>
    <div style="display:flex;align-items:center;gap:10px;background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.25);border-radius:14px;padding:10px 18px;flex-shrink:0;">
        <span style="width:10px;height:10px;border-radius:50%;background:var(--warning);display:inline-block;"></span>
        <span style="font-size:0.8rem;font-weight:800;color:var(--warning);">PAUSE</span>
        <span id="bar-pause" style="font-size:1.3rem;font-weight:900;color:#fff;font-family:'JetBrains Mono',monospace;"><?= $tv_paused ?></span>
        <span style="font-size:0.7rem;color:#64748b;">unit</span>
    </div>
    <div style="display:flex;align-items:center;gap:10px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:14px;padding:10px 18px;flex-shrink:0;">
        <span style="width:10px;height:10px;border-radius:50%;background:#475569;display:inline-block;"></span>
        <span style="font-size:0.8rem;font-weight:800;color:#64748b;">FREE</span>
        <span id="bar-free" style="font-size:1.3rem;font-weight:900;color:#fff;font-family:'JetBrains Mono',monospace;"><?= $tv_available ?></span>
        <span style="font-size:0.7rem;color:#64748b;">unit</span>
    </div>
    <div style="display:flex;align-items:center;gap:10px;background:rgba(6,182,212,0.08);border:1px solid rgba(6,182,212,0.2);border-radius:14px;padding:10px 18px;flex-shrink:0;">
        <i class='bx bx-tv' style="color:var(--primary);font-size:1.1rem;"></i>
        <span style="font-size:0.8rem;font-weight:800;color:var(--primary);">TOTAL</span>
        <span style="font-size:1.3rem;font-weight:900;color:#fff;font-family:'JetBrains Mono',monospace;"><?= $total_tv ?></span>
        <span style="font-size:0.7rem;color:#64748b;">unit</span>
    </div>
</div>







<div class="grid-units">
    <?php foreach ($tvs_arr as $tv): ?>
        <?php
        $status     = $tv['status'];
        $status_adb = checkADBStatus($tv['ip_address'], $adb_list);

        $plug_ids   = $_tuya_plug_map[$tv['id']] ?? [];
        $plug_count = count($plug_ids);

        $sisa_detik = 0; $elapsed = 0; $jenis_paket = 'fixed'; $fnb_total = 0; $is_expired = false;

        $has_trx = ($status != 'available' && !empty($tv['current_trx_id']));

        if ($has_trx) {
            $trx_id_local = (int)$tv['current_trx_id'];
            $jenis_paket  = $tv['jenis_paket'];
            $fnb_total    = $_fnb_map[$trx_id_local] ?? 0;

            if ($jenis_paket == 'fixed') {
                $now              = time();
                $target_timestamp = strtotime($tv['target_selesai']);
                if ($status == 'active') {
                    $sisa_detik = $target_timestamp - $now;
                    $is_expired = ($sisa_detik <= 0);
                    $sisa_detik = max(0, $sisa_detik);
                } else {
                    $sisa_detik = max(0, (int)$tv['remaining_seconds']);
                }
            } else {
                if ($status == 'paused') {
                    $elapsed = max(0, (int)$tv['remaining_seconds']);
                } else {
                    $elapsed = max(0, time() - strtotime($tv['start_time']));
                }
            }
        }

        $tv_id_safe       = (int)$tv['id'];
        $tv_nama_safe     = htmlspecialchars($tv['nama_tv'], ENT_QUOTES, 'UTF-8');
        $player_name_safe = htmlspecialchars($tv['nama_member'] ?? "Tamu", ENT_QUOTES, 'UTF-8');

        $status_icon = 'bx-circle';
        if ($status == 'active') $status_icon = 'bx-play-circle';
        elseif ($status == 'paused') $status_icon = 'bx-pause-circle';
        ?>

        <div class="unit-card <?php echo htmlspecialchars($status); ?>" id="card-<?php echo $tv_id_safe; ?>">
            <div class="card-header">
                <div class="tv-info">
                    <div class="tv-icon"><i class='bx bx-tv'></i></div>
                    <div>
                        <div class="tv-title"><?php echo $tv_nama_safe; ?></div>
                        <div class="adb-status <?php echo $status_adb; ?>" id="adb-status-<?php echo $tv_id_safe; ?>">
                            <span class="adb-dot"></span>
                            <span class="adb-text">ADB <?php echo strtoupper($status_adb); ?></span>
                        </div>
                        <?php if ($plug_count > 0): ?>
                        <button class="plug-toggle-btn plug-loading"
                                id="plug-btn-<?= $tv_id_safe ?>"
                                data-tv-id="<?= $tv_id_safe ?>"
                                data-plug-ids="<?= htmlspecialchars(json_encode($plug_ids)) ?>"
                                data-plug-state="unknown"
                                onclick="togglePlugFromDashboard(<?= $tv_id_safe ?>)"
                                title="Klik untuk ON/OFF smart plug">
                            <span id="plug-dot-<?= $tv_id_safe ?>" style="width:7px;height:7px;border-radius:50%;background:#475569;display:inline-block;"></span>
                            <span id="plug-text-<?= $tv_id_safe ?>">⚡ PLUG …</span>
                        </button>
                        <?php endif; ?>
                    </div>
                </div>
                <span class="status-pill" id="statuspill-<?php echo $tv_id_safe; ?>">
                    <i class='bx <?php echo $status_icon; ?>'></i>
                    <?php echo strtoupper($status); ?>
                </span>
            </div>

            <div class="timer-box">
                <div class="timer-box-inner">
                    <span class="timer-digits <?php echo $is_expired ? 'time-expired' : ''; ?>"
                        id="timer-<?php echo $tv_id_safe; ?>"
                        data-id="<?php echo $tv_id_safe; ?>"
                        data-status="<?php echo htmlspecialchars($status); ?>"
                        data-type="<?php echo htmlspecialchars($jenis_paket); ?>"
                        data-sisa="<?php echo (int)$sisa_detik; ?>"
                        data-expired="<?php echo $is_expired ? '1' : '0'; ?>"
                        data-elapsed="<?php echo (int)$elapsed; ?>"
                        data-rate="<?php echo (int)($tv['rate_per_jam'] ?? $default_rate); ?>"
                        data-min="<?php echo (int)$min_charge; ?>"
                        data-round="<?php echo (int)$round_val; ?>"
                        data-trx-id="<?php echo (int)($tv['current_trx_id'] ?? 0); ?>"><?php
                        if ($status == 'available') {
                            echo '00:00:00';
                        } elseif ($is_expired) {
                            echo 'WAKTU HABIS';
                        } elseif ($jenis_paket == 'fixed') {
                            printf("%02d:%02d:%02d", floor($sisa_detik/3600), floor(($sisa_detik%3600)/60), $sisa_detik%60);
                        } else {
                            printf("%02d:%02d:%02d", floor($elapsed/3600), floor(($elapsed%3600)/60), $elapsed%60);
                        }
                    ?></span>

                    <span class="billing-text">
                        <?php if ($status == 'available'): ?>
                            <i class='bx bx-game-controller bx-tada'></i> READY TO PLAY
                        <?php elseif ($jenis_paket == 'open'): ?>
                            BILLING: <span class="billing-amount" id="bill-<?php echo $tv_id_safe; ?>">Rp 0</span>
                        <?php else: ?>
                            <i class='bx bx-time-five'></i> REMAINING TIME
                        <?php endif; ?>
                    </span>
                </div>
            </div>

            <?php if ($status == 'available'): ?>
                <div class="tab-container">
                    <button class="tab-btn tab-btn-<?php echo $tv_id_safe; ?> active" onclick="switchTab(this, <?php echo $tv_id_safe; ?>, 'fixed')">
                        <i class='bx bx-package'></i> PAKET
                    </button>
                    <button class="tab-btn tab-btn-<?php echo $tv_id_safe; ?>" onclick="switchTab(this, <?php echo $tv_id_safe; ?>, 'open')">
                        <i class='bx bx-timer'></i> OPEN
                    </button>
                </div>

                <!-- FORM FIXED -->
                <form id="form-fixed-<?php echo $tv_id_safe; ?>" action="process.php" method="POST">
                    <input type="hidden" name="action" value="start">
                    <input type="hidden" name="tv_id" value="<?php echo $tv_id_safe; ?>">
                    <div class="stik-selector">
                        <div class="stik-trigger" onclick="toggleStik(this)">
                            <i class='bx bx-joystick'></i><span>Pilih Stik...</span><i class='bx bx-chevron-down' style="margin-left:auto;"></i>
                        </div>
                        <div class="stik-dropdown">
                            <?php if (empty($sticks_data)): ?>
                                <div class="no-stik-msg"><i class='bx bx-info-circle'></i> Tidak ada stik tersedia</div>
                            <?php else: foreach($sticks_data as $stk): ?>
                                <div class="stik-item">
                                    <input type="checkbox" name="stik[]" value="<?= htmlspecialchars($stk['nama_stik']) ?>" id="stk_f_<?= $tv_id_safe ?>_<?= $stk['id'] ?>" onchange="updateStikText(this)">
                                    <label for="stk_f_<?= $tv_id_safe ?>_<?= $stk['id'] ?>"><i class='bx bxs-joystick'></i> <?= htmlspecialchars($stk['nama_stik']) ?></label>
                                </div>
                            <?php endforeach; endif; ?>
                        </div>
                    </div>
                    <select name="paket_id" class="modern-select" required><?php echo $pkg_options; ?></select>
                    <select name="member_id" class="modern-select"><?php echo $member_options; ?></select>
                    <div class="voucher-start-wrap" style="margin-bottom:12px;">
                        <div style="display:flex;gap:8px;align-items:center;">
                            <input type="text" name="voucher_menit_kode" id="voucher_menit_kode_fixed_<?= $tv_id_safe ?>"
                                   class="modern-select" style="margin-bottom:0;font-family:'JetBrains Mono',monospace;letter-spacing:1px;text-transform:uppercase;"
                                   placeholder="🎁 Kode voucher menit (opsional)"
                                   oninput="this.value=this.value.toUpperCase().replace(/[^A-Z0-9\-]/g,'')">
                            <button type="button" class="btn-cek-voucher" style="margin-bottom:0;"
                                    onclick="cekVoucherMenit(<?= $tv_id_safe ?>, 'fixed', this)">CEK</button>
                        </div>
                        <div id="vmenit_result_fixed_<?= $tv_id_safe ?>"
                             style="font-size:.78rem;margin-top:6px;display:none;padding:8px 12px;border-radius:10px;"></div>
                        <input type="hidden" name="voucher_menit_id" id="voucher_menit_id_fixed_<?= $tv_id_safe ?>">
                    </div>
                    <button type="button" class="btn-start" onclick="interceptStartSession(event, <?php echo $tv_id_safe; ?>, 'fixed')"><i class='bx bx-play'></i> START SESSION</button>
                </form>

                <!-- FORM OPEN -->
                <form id="form-open-<?php echo $tv_id_safe; ?>" action="process.php" method="POST" class="hidden">
                    <input type="hidden" name="action" value="start_bebas">
                    <input type="hidden" name="tv_id" value="<?php echo $tv_id_safe; ?>">
                    <div class="stik-selector">
                        <div class="stik-trigger" onclick="toggleStik(this)">
                            <i class='bx bx-joystick'></i><span>Pilih Stik...</span><i class='bx bx-chevron-down' style="margin-left:auto;"></i>
                        </div>
                        <div class="stik-dropdown">
                            <?php if (empty($sticks_data)): ?>
                                <div class="no-stik-msg"><i class='bx bx-info-circle'></i> Tidak ada stik tersedia</div>
                            <?php else: foreach($sticks_data as $stk): ?>
                                <div class="stik-item">
                                    <input type="checkbox" name="stik[]" value="<?= htmlspecialchars($stk['nama_stik']) ?>" id="stk_o_<?= $tv_id_safe ?>_<?= $stk['id'] ?>" onchange="updateStikText(this)">
                                    <label for="stk_o_<?= $tv_id_safe ?>_<?= $stk['id'] ?>"><i class='bx bxs-joystick'></i> <?= htmlspecialchars($stk['nama_stik']) ?></label>
                                </div>
                            <?php endforeach; endif; ?>
                        </div>
                    </div>
                    <input type="number" name="harga_per_jam" class="modern-select" value="<?php echo (int)$default_rate; ?>" min="0" placeholder="Rate per jam">
                    <select name="member_id" class="modern-select"><?php echo $member_options; ?></select>
                    <div class="voucher-start-wrap" style="margin-bottom:12px;">
                        <div style="display:flex;gap:8px;align-items:center;">
                            <input type="text" name="voucher_menit_kode" id="voucher_menit_kode_open_<?= $tv_id_safe ?>"
                                   class="modern-select" style="margin-bottom:0;font-family:'JetBrains Mono',monospace;letter-spacing:1px;text-transform:uppercase;"
                                   placeholder="🎁 Kode voucher menit (opsional)"
                                   oninput="this.value=this.value.toUpperCase().replace(/[^A-Z0-9\-]/g,'')">
                            <button type="button" class="btn-cek-voucher" style="margin-bottom:0;"
                                    onclick="cekVoucherMenit(<?= $tv_id_safe ?>, 'open', this)">CEK</button>
                        </div>
                        <div id="vmenit_result_open_<?= $tv_id_safe ?>"
                             style="font-size:.78rem;margin-top:6px;display:none;padding:8px 12px;border-radius:10px;"></div>
                        <input type="hidden" name="voucher_menit_id" id="voucher_menit_id_open_<?= $tv_id_safe ?>">
                    </div>
                    <button type="button" class="btn-start success" onclick="interceptStartSession(event, <?php echo $tv_id_safe; ?>, 'open')"><i class='bx bx-timer'></i> START OPEN BILL</button>
                </form>

            <?php else: ?>
                <div class="info-grid">
                    <div class="info-item">
                        <span class="info-label">Player</span>
                        <div class="info-value"><i class='bx bx-user'></i> <?= $player_name_safe ?></div>
                    </div>
                    <div class="info-item">
                        <span class="info-label">FnB Order</span>
                        <div class="info-value" style="color:var(--primary)"><i class='bx bx-food-menu'></i> Rp <?= number_format($fnb_total) ?></div>
                    </div>
                </div>

                <div class="action-grid">
                    <button onclick="togglePause(<?= $tv_id_safe ?>, '<?= $status ?>')"
                            class="btn-act" id="pausebtn-<?= $tv_id_safe ?>">
                        <i class='bx bx-<?php echo $status == 'paused' ? 'play' : 'pause'; ?>'></i>
                        <span><?php echo $status == 'paused' ? 'Resume' : 'Pause'; ?></span>
                    </button>
                    <button onclick="openFnBModal(<?= $tv_id_safe ?>, '<?= $tv_nama_safe ?>')" class="btn-act">
                        <i class='bx bx-plus-circle'></i><span>FnB</span>
                    </button>
                    <?php if ($jenis_paket == 'fixed'): ?>
                    <button onclick="openAddTimeModal(<?= $tv_id_safe ?>, '<?= $tv_nama_safe ?>')" class="btn-act addtime">
                        <i class='bx bx-time-five'></i><span>+Waktu</span>
                    </button>
                    <?php endif; ?>
                    <button onclick="openMoveModal(<?= $tv_id_safe ?>)" class="btn-act">
                        <i class='bx bx-transfer'></i><span>Pindah</span>
                    </button>
                    <button onclick="openStopModal(<?= $tv_id_safe ?>, '<?= $tv_nama_safe ?>')" class="btn-act stop">
                        <i class='bx bx-stop-circle'></i><span>Stop</span>
                    </button>
                </div>
            <?php endif; ?>

            <div class="remote-dock">
                <button onclick="connectTV(event, <?= $tv_id_safe ?>, '<?= $tv_nama_safe ?>')" class="remote-btn connect-btn" id="connect-btn-<?= $tv_id_safe ?>" title="Connect ADB" data-ip="<?= htmlspecialchars($tv['ip_address']) ?>"><i class='bx bx-link'></i></button>
                <button onclick="remote(event, 'adb_power', <?= $tv_id_safe ?>)" class="remote-btn" title="Power"><i class='bx bx-power-off'></i></button>
                <button onclick="remote(event, 'adb_vol_down', <?= $tv_id_safe ?>)" class="remote-btn" title="Vol Down"><i class='bx bx-volume-low'></i></button>
                <button onclick="remote(event, 'adb_vol_up', <?= $tv_id_safe ?>)" class="remote-btn" title="Vol Up"><i class='bx bx-volume-full'></i></button>
                <button onclick="remote(event, 'adb_home', <?= $tv_id_safe ?>)" class="remote-btn" title="Home"><i class='bx bx-home-alt'></i></button>
            </div>
        </div>
    <?php endforeach; ?>
</div>

<!-- ================================================================
     MODAL START PAYMENT CHECK
     ================================================================ -->
<div id="modalStartPayCheck" class="modal-backdrop">
    <div class="modal-content" style="max-width:460px;">
        <h3 style="color:var(--primary);"><i class='bx bx-wallet-alt'></i> Konfirmasi Pembayaran</h3>
        <div id="startpay_info" style="background:rgba(6,182,212,0.07);border:1px solid rgba(6,182,212,0.2);border-radius:16px;padding:16px;margin-bottom:20px;text-align:center;">
            <div style="font-size:1rem;font-weight:700;margin-bottom:4px;" id="startpay_tvname">TV 01</div>
            <div style="font-size:.8rem;color:var(--text-dim);" id="startpay_paket">1 Paket</div>
        </div>
        <div style="font-size:.78rem;font-weight:800;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:12px;"><i class='bx bx-question-mark'></i> Pelanggan sudah bayar?</div>
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px;">
            <button id="btn_sudah_bayar" onclick="pilihSudahBayar()" class="btn-act" style="flex-direction:column;gap:6px;padding:20px 10px;border:2px solid rgba(16,185,129,0.3);background:rgba(16,185,129,0.07);color:var(--success);font-size:.85rem;">
                <i class='bx bx-check-shield' style="font-size:2rem;"></i>
                <span style="font-weight:800;">SUDAH BAYAR</span>
                <span style="font-size:.7rem;color:var(--text-dim);font-weight:500;">Pilih metode → mulai</span>
            </button>
            <button id="btn_belum_bayar" onclick="pilihBelumBayar()" class="btn-act" style="flex-direction:column;gap:6px;padding:20px 10px;border:2px solid rgba(245,158,11,0.3);background:rgba(245,158,11,0.07);color:var(--warning);font-size:.85rem;">
                <i class='bx bx-time-five' style="font-size:2rem;"></i>
                <span style="font-weight:800;">BELUM BAYAR</span>
                <span style="font-size:.7rem;color:var(--text-dim);font-weight:500;">Sesi jalan dulu, bayar nanti</span>
            </button>
        </div>
        <button onclick="closeModal('modalStartPayCheck')" class="btn-act" style="width:100%;justify-content:center;"><i class='bx bx-x'></i> Batal</button>
    </div>
</div>

<!-- ================================================================
     MODAL PILIH METODE PEMBAYARAN
     ================================================================ -->
<div id="modalPilihMetode" class="modal-backdrop">
    <div class="modal-content" style="max-width:440px;">
        <h3 style="color:var(--success);"><i class='bx bx-credit-card-alt'></i> Pilih Metode Pembayaran</h3>
        <div style="font-size:.78rem;font-weight:800;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:14px;">Bagaimana pelanggan membayar?</div>
        <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:20px;" id="metode_pilih_grid"></div>
        <div style="display:flex;gap:10px;">
            <button onclick="kembaliKePayCheck()" class="btn-act" style="flex:1"><i class='bx bx-arrow-back'></i> Kembali</button>
            <button onclick="konfirmasiStartDenganMetode()" id="btn_konfirmasi_metode" class="btn-start success" style="flex:1.5;display:flex;align-items:center;justify-content:center;gap:8px;">
                <i class='bx bx-play'></i> MULAI SESI
            </button>
        </div>
    </div>
</div>

<!-- ================================================================
     MODAL BELUM BAYAR EXPIRED
     ================================================================ -->
<div id="modalBelumBayarExpired" class="modal-backdrop">
    <div class="modal-content" style="max-width:460px;">
        <div style="background:linear-gradient(135deg,rgba(239,68,68,0.15),rgba(239,68,68,0.07));border:1px solid rgba(239,68,68,0.4);border-radius:16px;padding:20px;margin-bottom:20px;text-align:center;animation:warningPulse 2s ease-in-out infinite;">
            <div style="font-size:2.5rem;margin-bottom:8px;">⚠️</div>
            <div style="color:var(--danger);font-weight:900;font-size:1.1rem;margin-bottom:6px;">TV INI BELUM BAYAR!</div>
            <div style="color:#f87171;font-size:.85rem;" id="bbexp_tvname">TV 01 — sesi telah berakhir</div>
        </div>
        <div style="font-size:.78rem;font-weight:800;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:14px;"><i class='bx bx-credit-card-alt'></i> Pilih metode pembayaran sekarang:</div>
        <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:20px;" id="bbexp_metode_grid"></div>
        <button onclick="konfirmasiBayarSetelahSesi()" id="btn_bayar_setelah_sesi" class="btn-start" style="width:100%;display:flex;align-items:center;justify-content:center;gap:10px;font-size:.95rem;">
            <i class='bx bx-check-circle'></i> KONFIRMASI PEMBAYARAN & SELESAI
        </button>
    </div>
</div>

<!-- ================================================================
     MODAL STOP
     ================================================================ -->
<div id="modalStop" class="modal-backdrop">
    <div class="modal-content" style="max-width:480px;">
        <h3 style="color:var(--danger);"><i class='bx bxs-receipt'></i> Konfirmasi Billing</h3>

        <div id="billing_detail" class="receipt-box">Loading...</div>
        <div id="saldo_warning_box" style="display:none; margin-bottom:14px;"></div>

        <!-- ── VOUCHER BOX ── -->
        <div id="voucher_box" style="margin-bottom:16px; display:none;">
            <div style="font-size:.72rem;color:var(--text-dim);font-weight:700;
                        text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;">
                <i class='bx bx-gift'></i> Kode Voucher (Opsional)
            </div>
            <div class="voucher-input-wrap">
                <input type="text" id="voucher_kode_input"
                       placeholder="Masukkan kode voucher..."
                       maxlength="30"
                       oninput="this.value=this.value.toUpperCase().replace(/[^A-Z0-9\-]/g,'')">
                <button class="btn-cek-voucher" id="btn_cek_voucher" onclick="cekVoucher()">
                    <i class='bx bx-search-alt'></i> CEK
                </button>
            </div>
            <div class="voucher-result-box" id="voucher_result_box">
                <i class='bx bx-check-circle' id="voucher_result_icon"></i>
                <div style="flex:1">
                    <div id="voucher_result_text" style="font-weight:700;"></div>
                    <div id="voucher_result_sub" style="font-size:.72rem;margin-top:2px;opacity:.8;"></div>
                </div>
                <button class="voucher-clear-btn" onclick="clearVoucher()">✕ Hapus</button>
            </div>
        </div>

        <!-- ── PAYMENT METHOD BOX ── -->
        <div id="payment_method_box" style="margin-bottom:16px; display:none;">
            <div style="font-size:.72rem;color:var(--text-dim);font-weight:700;
                        text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;">
                <i class='bx bx-credit-card-alt'></i> Metode Pembayaran
            </div>
            <div id="payment_method_grid"
                 style="display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px;">
            </div>
        </div>

        <div style="display:flex; gap:12px;">
            <button onclick="closeModal('modalStop')" class="btn-act" style="flex:1"><i class='bx bx-x'></i> Batal</button>
            <button id="confirm_stop_btn" onclick="confirmStop()" class="btn-start success" style="flex:1.5; display:flex; align-items:center; justify-content:center; gap:8px;">
                <i class='bx bx-check'></i> <span id="stop_btn_label">BAYAR & STOP</span>
            </button>
        </div>
    </div>
</div>

<!-- MODAL FNB -->
<div id="modalFnB" class="modal-backdrop">
    <div class="modal-content">
        <h3 id="fnb_title" style="color:var(--primary);"><i class='bx bx-food-menu'></i> Order FnB</h3>
        <form id="fnb_form" action="process.php" method="POST" style="margin-top:20px;">
            <input type="hidden" name="action" value="order_fnb">
            <input type="hidden" name="tv_id" id="fnb_tv_id">
            <select name="snack_id" class="modern-select" required><?php echo $snack_options; ?></select>
            <input type="number" name="qty" id="fnb_qty" class="modern-select" value="1" min="1" max="99" placeholder="Jumlah">
            <div style="display:flex; gap:12px; margin-top:10px;">
                <button type="button" onclick="closeModal('modalFnB')" class="btn-act" style="flex:1"><i class='bx bx-x'></i> Batal</button>
                <button type="submit" class="btn-start" style="flex:1.5"><i class='bx bx-cart-add'></i> Tambah</button>
            </div>
        </form>
    </div>
</div>

<!-- MODAL MOVE -->
<div id="modalMove" class="modal-backdrop">
    <div class="modal-content">
        <h3 style="color:var(--primary);"><i class='bx bx-transfer'></i> Pindah Unit</h3>
        <form action="process.php" method="POST" style="margin-top:20px;">
            <input type="hidden" name="action" value="move">
            <input type="hidden" name="from_tv_id" id="move_from">
            <select name="to_tv_id" class="modern-select" required>
                <?php if (empty($available_tvs)): ?>
                    <option value="">Tidak ada TV tersedia</option>
                <?php else: foreach($available_tvs as $avt): ?>
                    <option value="<?= htmlspecialchars($avt['id']) ?>"><?= htmlspecialchars($avt['nama_tv']) ?></option>
                <?php endforeach; endif; ?>
            </select>
            <div style="display:flex; gap:12px;">
                <button type="button" onclick="closeModal('modalMove')" class="btn-act" style="flex:1"><i class='bx bx-x'></i> Batal</button>
                <button type="submit" class="btn-start" style="flex:1.5" <?= empty($available_tvs) ? 'disabled' : '' ?>><i class='bx bx-check'></i> Pindahkan</button>
            </div>
        </form>
    </div>
</div>

<!-- MODAL ADD TIME -->
<div id="modalAddTime" class="modal-backdrop">
    <div class="modal-content">
        <h3 style="color:var(--primary);"><i class='bx bx-time-five'></i> Tambah Waktu</h3>
        <div id="addtime_tv_label" style="text-align:center; color:var(--text-dim); font-size:0.85rem; margin-bottom:15px;"></div>
        <input type="hidden" id="addtime_tv_id">
        <select class="modern-select" id="addtime_paket_id" required><?php echo $pkg_addtime_options; ?></select>
        <div id="addtime_preview" style="background:rgba(6,182,212,0.1); border:1px solid rgba(6,182,212,0.2); border-radius:14px; padding:14px; margin-bottom:15px; font-size:0.85rem;">
            <div style="display:flex; justify-content:space-between; margin-bottom:6px;">
                <span style="color:var(--text-dim);">Tambah waktu:</span>
                <span id="addtime_preview_menit" style="color:var(--primary); font-weight:700;">-</span>
            </div>
            <div style="display:flex; justify-content:space-between;">
                <span style="color:var(--text-dim);">Tambah biaya:</span>
                <span id="addtime_preview_harga" style="color:var(--success); font-weight:700;">-</span>
            </div>
        </div>
        <div style="display:flex; gap:12px;">
            <button type="button" onclick="closeModal('modalAddTime')" class="btn-act" style="flex:1"><i class='bx bx-x'></i> Batal</button>
            <button type="button" onclick="confirmAddTime()" id="addtime_submit_btn" class="btn-start" style="flex:1.5; display:flex; align-items:center; justify-content:center; gap:8px;">
                <i class='bx bx-plus'></i> TAMBAH WAKTU
            </button>
        </div>
    </div>
</div>

<!-- MODAL SALDO KURANG -->
<div id="modalSaldoKurang" class="modal-backdrop">
    <div class="modal-content" style="max-width:440px;">
        <h3 style="color:var(--warning);"><i class='bx bxs-error-alt'></i> Saldo Tidak Mencukupi</h3>
        <div id="saldo_kurang_detail"></div>
        <div style="display:flex; flex-direction:column; gap:10px; margin-top:16px;">
            <button onclick="pilihanTopUp()" class="btn-start" style="background:linear-gradient(135deg,var(--primary),var(--primary-dark)); display:flex; align-items:center; justify-content:center; gap:8px;">
                <i class='bx bx-wallet-alt'></i> TOP-UP SALDO DULU
            </button>
            <button onclick="pilihanBayarCash()" class="btn-start" style="background:linear-gradient(135deg,var(--success),#059669); color:#fff; display:flex; align-items:center; justify-content:center; gap:8px;">
                <i class='bx bx-money'></i> BAYAR CASH (LANJUT TANPA POTONG SALDO)
            </button>
            <button onclick="closeModal('modalSaldoKurang')" class="btn-act" style="justify-content:center;">
                <i class='bx bx-x'></i> Batal
            </button>
        </div>
    </div>
</div>

<!-- MODAL TOP-UP -->
<div id="modalTopUp" class="modal-backdrop">
    <div class="modal-content" style="max-width:400px;">
        <h3 style="color:var(--primary);"><i class='bx bx-wallet-alt'></i> Top-Up Saldo Member</h3>
        <div id="topup_member_info" style="text-align:center; color:var(--text-dim); margin-bottom:15px; font-size:.85rem; background:rgba(6,182,212,0.08); border:1px solid rgba(6,182,212,0.2); border-radius:12px; padding:12px;"></div>
        <input type="hidden" id="topup_member_id_input">
        <input type="number" id="topup_amount" class="modern-select" placeholder="Jumlah top-up (Rp)" min="1000" step="1000">
        <div style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px;">
            <button onclick="addTopupAmount(10000)"  class="topup-quick-btn">+10rb</button>
            <button onclick="addTopupAmount(20000)"  class="topup-quick-btn">+20rb</button>
            <button onclick="addTopupAmount(50000)"  class="topup-quick-btn">+50rb</button>
            <button onclick="addTopupAmount(100000)" class="topup-quick-btn">+100rb</button>
        </div>
        <div style="display:flex; gap:12px;">
            <button onclick="closeModal('modalTopUp')" class="btn-act" style="flex:1"><i class='bx bx-x'></i> Batal</button>
            <button onclick="confirmTopUp()" id="topup_submit_btn" class="btn-start success" style="flex:1.5; display:flex; align-items:center; justify-content:center; gap:8px; color:#fff;">
                <i class='bx bx-check'></i> KONFIRMASI TOP-UP
            </button>
        </div>
    </div>
</div>

<script>
// ===================================================================
// STATE GLOBAL
// ===================================================================
let currentStopTvId      = null;
let currentStopTrxId     = null;
let alreadyStopped       = false;
let stopNeedCash         = 0;
let stopMemberSaldo      = 0;
let stopGrandTotal       = 0;
let _stopGrandTotalAsli  = 0;
const stoppedTrxMap      = {};
const pendingTimeouts    = {};
const expiredQueue       = {};
let pendingStartForm     = null;
let pendingMemberId      = null;
let pendingKekurangan    = 0;
let pendingMemberSaldo   = 0;
let pendingHargaPaket    = 0;
let pendingTopUpAfterFnB = null;
let selectedPaymentMethod = 'Cash';
const _belumBayarTvIds   = {};

// ── Voucher state ──
let _voucherId    = null;
let _voucherDiskon = 0;
let _voucherKode  = '';

function escapeHtml(text) {
    if (text == null) return '';
    const d = document.createElement('div');
    d.textContent = text;
    return d.innerHTML;
}

// ===================================================================
// PARTICLES
// ===================================================================
function createParticles() {
    const container = document.getElementById('particles');
    if (!container) return;
    const frag = document.createDocumentFragment();
    for (let i = 0; i < 12; i++) {
        const p = document.createElement('div');
        p.className = 'particle';
        p.style.left = Math.random() * 100 + '%';
        p.style.animationDelay = Math.random() * 10 + 's';
        p.style.animationDuration = (8 + Math.random() * 6) + 's';
        frag.appendChild(p);
    }
    container.appendChild(frag);
}
createParticles();

// ===================================================================
// TAB & STIK
// ===================================================================
function switchTab(btn, id, type) {
    document.getElementById(`form-fixed-${id}`)?.classList.toggle('hidden', type !== 'fixed');
    document.getElementById(`form-open-${id}`)?.classList.toggle('hidden', type !== 'open');
    document.querySelectorAll(`.tab-btn-${id}`).forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
}

function toggleStik(el) {
    document.querySelectorAll('.stik-selector.active').forEach(s => {
        if (s !== el.closest('.stik-selector')) s.classList.remove('active');
    });
    el.closest('.stik-selector').classList.toggle('active');
}

function updateStikText(input) {
    const selector = input.closest('.stik-selector');
    const checked  = Array.from(selector.querySelectorAll('input:checked')).map(i => i.value);
    const span     = selector.querySelector('.stik-trigger span');
    if (span) span.innerText = checked.length ? checked.join(', ') : 'Pilih Stik...';
}

// ===================================================================
// DOM READY
// ===================================================================
document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('form[id^="form-fixed-"], form[id^="form-open-"]').forEach(form => {
        form.addEventListener('submit', function(e) {
            const memberSelect = this.querySelector('select[name="member_id"]');
            const memberId = memberSelect ? memberSelect.value : '';
            if (!memberId) return;
            if (this.querySelector('[name="bypass_saldo"]')) return;
            e.preventDefault();
            pendingStartForm = this;
            const fd  = new FormData(this);
            const submitBtn = this.querySelector('button[type="submit"]');
            const origHtml  = submitBtn ? submitBtn.innerHTML : '';
            if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Mengecek...'; }
            fetch('process.php?ajax=1', { method: 'POST', body: fd })
                .then(r => r.json())
                .then(data => {
                    if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = origHtml; }
                    if (data.status === 'saldo_kurang') { showSaldoKurangModal(data); } else { this.submit(); }
                })
                .catch(() => { if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = origHtml; } this.submit(); });
        });
    });

    const fnbForm = document.getElementById('fnb_form');
    if (fnbForm) {
        fnbForm.addEventListener('submit', function(e) {
            const tvId     = document.getElementById('fnb_tv_id').value;
            const snackSel = this.querySelector('select[name="snack_id"]');
            const qty      = parseInt(document.getElementById('fnb_qty').value) || 1;
            const harga    = snackSel ? parseInt(snackSel.options[snackSel.selectedIndex].dataset.harga || 0) : 0;
            const total    = harga * qty;
            if (this.querySelector('[name="bypass_saldo"]')) return;
            e.preventDefault();
            fetch(`process.php?action=get_member_from_tv&tv_id=${tvId}&ajax=1`)
                .then(r => r.json())
                .then(md => {
                    if (!md.member_id) { this.submit(); return; }
                    if (md.saldo < total) {
                        showSaldoKurangFnBModal({ kekurangan: total - md.saldo, member_saldo: md.saldo, total_fnb: total, member_id: md.member_id, member_name: md.member_name, tv_id: tvId });
                    } else { this.submit(); }
                })
                .catch(() => this.submit());
        });
    }

    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.get('status') === 'saldo_kurang_fnb') {
        showSaldoKurangFnBModal({ kekurangan: parseInt(urlParams.get('kekurangan')||0), member_saldo: parseInt(urlParams.get('member_saldo')||0), total_fnb: parseInt(urlParams.get('total_fnb')||0), tv_id: urlParams.get('tv_id'), member_id: null, member_name: 'Member' });
        history.replaceState({}, '', 'index.php');
    }
    document.getElementById('addtime_paket_id')?.addEventListener('change', updateAddTimePreview);
});

// ===================================================================
// SALDO KURANG
// ===================================================================
function showSaldoKurangModal(data) {
    pendingMemberId    = data.member_id || null;
    pendingKekurangan  = data.kekurangan || 0;
    pendingMemberSaldo = data.member_saldo || 0;
    pendingHargaPaket  = data.harga_paket || data.min_charge || 0;
    const namaInfo   = data.nama_paket ? `<b style="color:var(--primary);">${escapeHtml(data.nama_paket)}</b>` : `Min. Charge`;
    const memberName = escapeHtml(data.member_name || 'Member');
    document.getElementById('saldo_kurang_detail').innerHTML = `
        <div style="background:rgba(245,158,11,0.08);border:1px solid rgba(245,158,11,0.3);border-radius:16px;padding:16px;margin-bottom:4px;">
            <div style="text-align:center;margin-bottom:14px;">
                <div style="width:52px;height:52px;background:rgba(245,158,11,0.15);border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 8px;">
                    <i class='bx bx-user' style="font-size:1.6rem;color:var(--warning);"></i></div>
                <div style="font-weight:700;font-size:1rem;">${memberName}</div></div>
            <div class="billing-item"><span style="color:var(--text-dim);">Biaya</span><span style="color:#fff;font-weight:700;">${namaInfo} — Rp ${pendingHargaPaket.toLocaleString('id-ID')}</span></div>
            <div class="billing-item"><span style="color:var(--text-dim);">Saldo tersedia</span><span style="color:var(--warning);font-weight:700;">Rp ${pendingMemberSaldo.toLocaleString('id-ID')}</span></div>
            <div style="display:flex;justify-content:space-between;padding-top:12px;border-top:1px solid rgba(239,68,68,0.3);margin-top:4px;">
                <span style="color:#f87171;font-weight:800;">💸 Kekurangan</span>
                <span style="color:var(--danger);font-weight:900;font-size:1.2rem;font-family:'JetBrains Mono',monospace;">Rp ${pendingKekurangan.toLocaleString('id-ID')}</span></div>
        </div>
        <div style="text-align:center;color:var(--text-dim);font-size:.8rem;margin-top:10px;">Pilih opsi pembayaran di bawah:</div>`;
    document.getElementById('modalSaldoKurang').style.display = 'flex';
}

function pilihanTopUp() {
    closeModal('modalSaldoKurang');
    document.getElementById('topup_member_id_input').value = pendingMemberId || '';
    document.getElementById('topup_member_info').innerHTML = `Top-up untuk melanjutkan sesi.<br>Minimal: <b style="color:var(--danger);">Rp ${pendingKekurangan.toLocaleString('id-ID')}</b>`;
    document.getElementById('topup_amount').value = pendingKekurangan;
    pendingTopUpAfterFnB = null;
    document.getElementById('modalTopUp').style.display = 'flex';
}

function pilihanBayarCash() {
    closeModal('modalSaldoKurang');
    if (pendingStartForm) {
        const old = pendingStartForm.querySelector('[name="bypass_saldo"]');
        if (old) old.remove();
        const bypass = document.createElement('input');
        bypass.type = 'hidden'; bypass.name = 'bypass_saldo'; bypass.value = '1';
        pendingStartForm.appendChild(bypass);
        pendingStartForm.submit();
    }
}

function addTopupAmount(amount) {
    const input = document.getElementById('topup_amount');
    input.value = (parseInt(input.value) || 0) + amount;
}

function confirmTopUp() {
    const memberId = document.getElementById('topup_member_id_input').value;
    const amount   = parseInt(document.getElementById('topup_amount').value);
    if (!memberId || !amount || amount < 1) { showToast('✗ Masukkan jumlah top-up yang valid', 'error'); return; }
    const btn = document.getElementById('topup_submit_btn');
    btn.disabled = true; btn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Memproses...';
    const fd = new FormData();
    fd.append('action', 'topup_saldo'); fd.append('member_id', memberId); fd.append('amount', amount);
    fetch('process.php', { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            if (data.status === 'success') {
                showToast(`✓ Top-up Rp ${amount.toLocaleString('id-ID')} berhasil!`, 'success');
                closeModal('modalTopUp');
                btn.disabled = false; btn.innerHTML = '<i class="bx bx-check"></i> KONFIRMASI TOP-UP';
                if (pendingTopUpAfterFnB) { const form = document.getElementById('fnb_form'); if (form) { const old = form.querySelector('[name="bypass_saldo"]'); if (old) old.remove(); setTimeout(() => form.submit(), 400); } pendingTopUpAfterFnB = null; }
                else if (pendingStartForm) { setTimeout(() => pendingStartForm.submit(), 400); }
            } else {
                showToast(`✗ ${data.msg || 'Gagal top-up'}`, 'error');
                btn.disabled = false; btn.innerHTML = '<i class="bx bx-check"></i> KONFIRMASI TOP-UP';
            }
        })
        .catch(() => { showToast('✗ Gagal top-up', 'error'); btn.disabled = false; btn.innerHTML = '<i class="bx bx-check"></i> KONFIRMASI TOP-UP'; });
}

function showSaldoKurangFnBModal(data) {
    Swal.fire({
        title: '⚠ Saldo Tidak Cukup untuk FnB',
        html: `<div style="text-align:left;font-size:.9rem;color:#94a3b8;line-height:1.8;">
            <div style="background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.3);border-radius:12px;padding:14px;margin-bottom:12px;">
                <div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span>Total FnB:</span><b style="color:#fff;">Rp ${(data.total_fnb||0).toLocaleString('id-ID')}</b></div>
                <div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span>Saldo tersedia:</span><b style="color:var(--warning);">Rp ${(data.member_saldo||0).toLocaleString('id-ID')}</b></div>
                <div style="display:flex;justify-content:space-between;border-top:1px solid rgba(239,68,68,0.3);padding-top:8px;margin-top:4px;">
                    <span style="color:#f87171;font-weight:800;">💸 Kekurangan:</span>
                    <b style="color:var(--danger);font-size:1.1rem;">Rp ${(data.kekurangan||0).toLocaleString('id-ID')}</b></div></div>
            <div style="color:#fbbf24;font-size:.82rem;font-weight:600;text-align:center;">Pilih opsi pembayaran</div></div>`,
        icon: null, showDenyButton: true, showCancelButton: true,
        confirmButtonText: '<i class="bx bx-wallet-alt"></i> Top-Up Saldo Dulu',
        denyButtonText: '<i class="bx bx-money"></i> Bayar Cash',
        cancelButtonText: 'Batal',
        confirmButtonColor: '#06b6d4', denyButtonColor: '#10b981', cancelButtonColor: '#334155',
        background: '#1e293b', color: '#fff',
    }).then(result => {
        if (result.isConfirmed) {
            document.getElementById('topup_member_id_input').value = data.member_id || '';
            document.getElementById('topup_member_info').innerHTML = `Top-up untuk FnB.<br>Minimal: <b style="color:var(--danger);">Rp ${(data.kekurangan||0).toLocaleString('id-ID')}</b>`;
            document.getElementById('topup_amount').value = data.kekurangan || '';
            pendingTopUpAfterFnB = data;
            document.getElementById('modalTopUp').style.display = 'flex';
        } else if (result.isDenied) {
            const form = document.getElementById('fnb_form');
            if (form) { const old = form.querySelector('[name="bypass_saldo"]'); if (old) old.remove(); const bypass = document.createElement('input'); bypass.type = 'hidden'; bypass.name = 'bypass_saldo'; bypass.value = '1'; form.appendChild(bypass); form.submit(); }
        }
    });
}






// ===================================================================
// UPDATE STATUS BAR REALTIME
// ===================================================================
function updateStatusBar() {
    const cards = document.querySelectorAll('.unit-card');
    let aktif = 0, pause = 0, free = 0;
    cards.forEach(card => {
        if (card.classList.contains('active'))         aktif++;
        else if (card.classList.contains('paused'))    pause++;
        else if (card.classList.contains('available')) free++;
    });

    // Update angka di status bar
    
    // Lebih aman pakai ID — tambahkan ID ke elemen status bar
    const barAktif  = document.getElementById('bar-aktif');
    const barPause  = document.getElementById('bar-pause');
    const barFree   = document.getElementById('bar-free');
    if (barAktif) barAktif.textContent = aktif;
    if (barPause) barPause.textContent = pause;
    if (barFree)  barFree.textContent  = free;
}



// ===================================================================
// TIMER SYSTEM
// ===================================================================
const timers = [];

function initTimers() {
    document.querySelectorAll('.timer-digits').forEach(el => {
        const type = el.dataset.type, status = el.dataset.status;
        if (status === 'available') return;
        const initVal   = (type === 'fixed') ? parseInt(el.dataset.sisa || 0) : parseInt(el.dataset.elapsed || 0);
        const roundVal  = parseInt(el.dataset.round) || 1;
        const isExpired = el.dataset.expired === '1';
        const t = { el, id: el.dataset.id, type, startTime: Date.now(), initSec: initVal,
            rate: parseInt(el.dataset.rate) || 0, min: parseInt(el.dataset.min) || 0,
            round: roundVal > 0 ? roundVal : 1,
            billEl: document.getElementById(`bill-${el.dataset.id}`),
            trxId: parseInt(el.dataset.trxId) || 0, done: isExpired };
        timers.push(t);
        if (isExpired && type === 'fixed') { el.innerHTML = 'WAKTU HABIS'; expiredQueue[t.id] = t.trxId; }
    });
    setInterval(tick, 1000);
}

function tick() {
    const now = Date.now();
    timers.forEach(t => {
        const status = t.el.dataset.status;
        if (status === 'paused')    { t.el.innerHTML = formatTime(t.initSec); return; }
        if (status === 'available') { t.el.innerHTML = '00:00:00'; return; }
        const delta = Math.floor((now - t.startTime) / 1000);
        let sec = (t.type === 'fixed') ? (t.initSec - delta) : (t.initSec + delta);
        if (sec < 0) sec = 0;
        t.el.innerHTML = formatTime(sec);
        if (t.type === 'fixed' && sec === 0 && !t.done) {
            // ← TAMBAH CEK INI
            if (t.el.dataset.status === 'paused') return;
            t.done = true;
            t.el.classList.add('time-expired');
            t.el.innerHTML = 'WAKTU HABIS';
            expiredQueue[t.id] = t.trxId;
        }
    });
}

// ===================================================================
// EXPIRED QUEUE
// ===================================================================
function processExpiredQueue() {
    const modalOpen = document.getElementById('modalStop').style.display === 'flex';
    if (modalOpen) return;
    const pending = Object.entries(expiredQueue);
    if (pending.length === 0) return;
    const [tvId, trxId] = pending[0];
    
const card = document.getElementById('card-' + tvId);
    if (card && card.classList.contains('paused')) {
        delete expiredQueue[tvId];
        return;
    }



    delete expiredQueue[tvId];
    const timerEl = document.getElementById(`timer-${tvId}`);
    
    if (timerEl) { timerEl.classList.add('time-expired'); timerEl.innerHTML = 'WAKTU HABIS'; }

    if (_belumBayarTvIds[tvId]) {
        fetch(`process.php?action=stop&tv_id=${tvId}&trx_id=${trxId}&ajax=1&auto_timeout=1`)
            .then(r => r.json())
            .then(data => {
                stoppedTrxMap[tvId] = data.trx_id || trxId;
                _saveExpiredToStorage(tvId, data.trx_id || trxId);
                refreshPlugAfterStop(tvId);
                _showBelumBayarExpiredModal(tvId, data.trx_id || trxId);
            })
            .catch(() => {
                stoppedTrxMap[tvId] = trxId;
                _saveExpiredToStorage(tvId, trxId);
                refreshPlugAfterStop(tvId);
                _showBelumBayarExpiredModal(tvId, trxId);
            });
    } else {
        fetch(`process.php?action=stop&tv_id=${tvId}&trx_id=${trxId}&ajax=1&auto_timeout=1`)
            .then(r => r.json())
            .then(data => {
                stoppedTrxMap[tvId] = data.trx_id || trxId;
                _saveExpiredToStorage(tvId, data.trx_id || trxId);
                _showExpiredNotif(tvId);
                refreshPlugAfterStop(tvId);
                const tvTitle = card?.querySelector('.tv-title')?.textContent || `TV ${tvId}`;
                openStopModal(tvId, tvTitle);
            })
            .catch(() => {
                stoppedTrxMap[tvId] = trxId;
                _saveExpiredToStorage(tvId, trxId);
                _showExpiredNotif(tvId);
                refreshPlugAfterStop(tvId);
            });
    }
}
setTimeout(() => setInterval(processExpiredQueue, 2000), 5000);

// ===================================================================
// SESSION STORAGE — Simpan & restore expired state antar tab/reload
// ===================================================================
function _saveExpiredToStorage(tvId, trxId) {
    try {
        let stored = JSON.parse(sessionStorage.getItem('_expired_pending') || '[]');
        if (!stored.find(s => s.tv_id == tvId)) {
            const card   = document.getElementById('card-' + tvId);
            const tvName = card?.querySelector('.tv-title')?.textContent || ('TV ' + tvId);
            stored.push({ tv_id: parseInt(tvId), trx_id: parseInt(trxId), nama_tv: tvName });
            sessionStorage.setItem('_expired_pending', JSON.stringify(stored));
        }
    } catch(e) {}
}

function _removeExpiredFromStorage(tvId) {
    try {
        let stored = JSON.parse(sessionStorage.getItem('_expired_pending') || '[]');
        stored = stored.filter(s => s.tv_id != tvId);
        if (stored.length === 0) sessionStorage.removeItem('_expired_pending');
        else sessionStorage.setItem('_expired_pending', JSON.stringify(stored));
    } catch(e) {}
}

// ===================================================================
// RESTORE EXPIRED STATE SAAT PAGE LOAD
// Gabungkan data dari PHP (TV masih aktif di DB) + sessionStorage
// (TV sudah di-stop server tapi kasir belum konfirmasi)
// ===================================================================
(function restoreExpiredOnLoad() {
    const fromPHP = <?php echo json_encode($expired_pending); ?>;

    let fromStorage = [];
    try {
        fromStorage = JSON.parse(sessionStorage.getItem('_expired_pending') || '[]');
    } catch(e) { fromStorage = []; }

    // Gabungkan, hindari duplikat
    const merged = [...fromPHP];
    fromStorage.forEach(s => {
        if (!merged.find(m => m.tv_id === s.tv_id)) merged.push(s);
    });

    if (merged.length === 0) {
        sessionStorage.removeItem('_expired_pending');
        return;
    }

    // Update sessionStorage dengan data terbaru
    sessionStorage.setItem('_expired_pending', JSON.stringify(merged));

    merged.forEach(function(item) {
        const timerEl = document.getElementById('timer-' + item.tv_id);
        const card    = document.getElementById('card-' + item.tv_id);

        if (timerEl && timerEl.dataset.status === 'paused') return;
        if (card && card.classList.contains('paused')) return;

        if (timerEl) {
            timerEl.classList.add('time-expired');
            timerEl.innerHTML = 'WAKTU HABIS';
            timerEl.dataset.status = 'active';
            timerEl.dataset.expired = '1';
        }
        if (card) {
            card.classList.remove('available');
            card.classList.add('active');
        }

        // Masukkan ke expiredQueue
        expiredQueue[item.tv_id] = item.trx_id;

        // Kalau dari storage (sudah di-stop server), tandai alreadyStopped
        if (!fromPHP.find(p => p.tv_id === item.tv_id)) {
            stoppedTrxMap[item.tv_id] = item.trx_id;
        }
    });

    // Jalankan segera setelah 1 detik
    setTimeout(processExpiredQueue, 1000);
})();

function _showExpiredNotif(tvId) {
    if (document.getElementById(`timeout-notif-${tvId}`)) return;
    
    // Ambil nama TV dari card, bukan pakai tvId mentah
    const card   = document.getElementById('card-' + tvId);
    const tvName = card?.querySelector('.tv-title')?.textContent || 'TV ' + tvId;
    
    const notif = document.createElement('div');
    notif.id = `timeout-notif-${tvId}`;
    notif.style.cssText = `position:fixed;top:20px;right:20px;z-index:99999;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;padding:20px 25px;border-radius:16px;box-shadow:0 10px 40px rgba(239,68,68,0.4);animation:slideIn .3s ease;font-weight:700;backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);min-width:320px;`;
    notif.innerHTML = `<div style="display:flex;align-items:center;gap:12px;"><i class='bx bx-power-off bx-tada' style="font-size:32px;"></i><div><div style="font-size:1.1rem;">⏰ ${tvName}: SELESAI!</div><div style="font-size:.8rem;opacity:.9;">Sistem telah mematikan TV otomatis.</div></div></div>`;
    document.body.appendChild(notif);
    setTimeout(() => { notif.style.animation = 'slideOut .3s ease'; setTimeout(() => notif.remove(), 300); }, 10000);
}
function formatTime(s) {
    if (s < 0) s = 0;
    return [Math.floor(s/3600), Math.floor((s%3600)/60), s%60].map(v => String(v).padStart(2,'0')).join(':');
}

// ===================================================================
// PAUSE / RESUME
// ===================================================================
function togglePause(tvId, currentStatus) {
    const action  = (currentStatus === 'paused') ? 'resume' : 'pause';
    const btn     = document.getElementById(`pausebtn-${tvId}`);
    const timerEl = document.getElementById(`timer-${tvId}`);
    if (btn) { btn.disabled = true; btn.style.opacity = '0.5'; }

    if (timerEl && action === 'pause') {
        // Pastikan string vs int tidak mismatch
        const timerObj = timers.find(t => String(t.id) === String(tvId));
        if (timerObj) {
            const delta = Math.floor((Date.now() - timerObj.startTime) / 1000);
            timerObj.initSec = (timerObj.type === 'fixed')
                ? Math.max(0, timerObj.initSec - delta)
                : timerObj.initSec + delta;
            timerObj.startTime = Date.now();
            timerEl.dataset.status = 'paused';
            timerEl.innerHTML = formatTime(timerObj.initSec); // ← dalam scope yang sama
        } else {
            // Fallback: baca dari data-sisa
            timerEl.dataset.status = 'paused';
        }
    }

fetch(`process.php?action=${action}&tv_id=${tvId}&ajax=1`)
    .then(r => r.json())
    .then(data => {
        if (data.status !== 'success') {
            showToast('✗ Gagal ' + (action === 'pause' ? 'pause' : 'resume'), 'error');
            if (btn) { btn.disabled = false; btn.style.opacity = ''; }
            return;
        }

        const timerEl = document.getElementById(`timer-${tvId}`);
        const card    = document.getElementById(`card-${tvId}`);
        const pill    = card?.querySelector('.status-pill');

        if (action === 'pause') {
            // kode pause existing tidak berubah
            timerEl.dataset.status = 'paused';
            card?.classList.remove('active'); card?.classList.add('paused');
            if (pill) pill.innerHTML = "<i class='bx bx-pause-circle'></i> PAUSED";
            if (btn) {
                btn.innerHTML = "<i class='bx bx-play'></i><span>Resume</span>";
                btn.onclick = () => togglePause(tvId, 'paused');
                btn.disabled = false; btn.style.opacity = '';
            }
            showToast('⏸ Sesi di-pause', 'warning');
             updateStatusBar(); // ← TAMBAH INI

        } else {
            // RESUME — update timer langsung dari nilai server
            const timerObj = timers.find(t => String(t.id) === String(tvId));
            if (timerObj && data.new_sisa_detik > 0) {
                timerObj.initSec   = data.new_sisa_detik;
                timerObj.startTime = Date.now();
                timerObj.done      = false;
                timerEl.dataset.status  = 'active';
                timerEl.dataset.expired = '0';
                timerEl.classList.remove('time-expired');
                timerEl.innerHTML = formatTime(data.new_sisa_detik);
            }

            card?.classList.remove('paused'); card?.classList.add('active');
            if (pill) pill.innerHTML = "<i class='bx bx-play-circle'></i> ACTIVE";
            if (btn) {
                btn.innerHTML = "<i class='bx bx-pause'></i><span>Pause</span>";
                btn.onclick = () => togglePause(tvId, 'active');
                btn.disabled = false; btn.style.opacity = '';
            }
            showToast('▶ Sesi dilanjutkan', 'success');
            updateStatusBar(); // ← TAMBAH INI JUGA
        }
    })
    .catch(() => window.location.reload());
}

// ===================================================================
// RENDER PAYMENT METHODS
// ===================================================================
function renderPaymentMethods(metodeList, preSelected) {
    const grid = document.getElementById('payment_method_grid');
    const box  = document.getElementById('payment_method_box');
    if (!grid || !metodeList || metodeList.length === 0) {
        if (box) box.style.display = 'none';
        return;
    }
    selectedPaymentMethod = preSelected || metodeList[0].nama;
    grid.innerHTML = '';
    metodeList.forEach((m) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        const isSelected = m.nama === selectedPaymentMethod;
        btn.className = 'pay-method-btn' + (isSelected ? ' selected' : '');
        btn.dataset.method = m.nama;
        btn.innerHTML = `<i class='bx ${escapeHtml(m.icon || 'bx-credit-card')}'></i>${escapeHtml(m.nama)}`;
        btn.addEventListener('click', function() {
            grid.querySelectorAll('.pay-method-btn').forEach(b => b.classList.remove('selected'));
            this.classList.add('selected');
            selectedPaymentMethod = this.dataset.method;
        });
        grid.appendChild(btn);
    });
    if (box) box.style.display = 'block';
}

// ===================================================================
// VOUCHER STATE MANAGEMENT
// ===================================================================
function resetVoucherState() {
    _voucherId     = null;
    _voucherDiskon = 0;
    _voucherKode   = '';
    const inp = document.getElementById('voucher_kode_input');
    if (inp) { inp.value = ''; inp.className = ''; }
    const rb = document.getElementById('voucher_result_box');
    if (rb) { rb.className = 'voucher-result-box'; rb.style.display = 'none'; }
    document.getElementById('voucher_diskon_row')?.remove();
}

function clearVoucher() {
    stopGrandTotal = _stopGrandTotalAsli;
    resetVoucherState();
    const totalAmt = document.querySelector('#billing_detail .total-row span:last-child');
    if (totalAmt) totalAmt.textContent = 'Rp ' + stopGrandTotal.toLocaleString('id-ID');
    _resetStopBtnLabel();
}

function _resetStopBtnLabel() {
    const btn   = document.getElementById('confirm_stop_btn');
    const lblEl = document.getElementById('stop_btn_label');
    if (!btn || !lblEl) return;
    if (alreadyStopped) {
        lblEl.textContent = '✓ TUTUP & SELESAI';
        btn.style.background = 'linear-gradient(135deg,var(--success),#059669)';
        btn.style.color = '#fff';
    } else {
        lblEl.textContent = 'BAYAR & STOP';
        btn.style.background = '';
        btn.style.color = '';
    }
}

function cekVoucher() {
    const inp  = document.getElementById('voucher_kode_input');
    const kode = inp.value.trim();
    if (!kode) { showToast('Masukkan kode voucher dulu', 'warning'); return; }

    const btn = document.getElementById('btn_cek_voucher');
    btn.disabled = true;
    btn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i>';

    const fd = new FormData();
    fd.append('action', 'validate_voucher');
    fd.append('kode',   kode);
    fd.append('total',  _stopGrandTotalAsli);
    if (currentStopTrxId) fd.append('trx_id', currentStopTrxId);

    fetch('process.php', { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            btn.disabled = false;
            btn.innerHTML = '<i class="bx bx-search-alt"></i> CEK';

            const rb   = document.getElementById('voucher_result_box');
            const rtxt = document.getElementById('voucher_result_text');
            const rsub = document.getElementById('voucher_result_sub');
            const rico = document.getElementById('voucher_result_icon');
            inp.classList.remove('v-valid', 'v-invalid');

            if (data.status !== 'success') {
                inp.classList.add('v-invalid');
                rb.className = 'voucher-result-box err';
                rico.className = 'bx bx-x-circle';
                rtxt.textContent = data.msg || 'Voucher tidak valid';
                rsub.textContent = '';
                if (_voucherId) clearVoucher();
                return;
            }

            _voucherId     = data.voucher_id;
            _voucherDiskon = data.diskon;
            _voucherKode   = data.kode;

            inp.classList.add('v-valid');
            rb.className = 'voucher-result-box ok';
            rico.className = 'bx bx-check-circle';
            rtxt.textContent = `✓ ${data.nama || data.kode} — ${data.diskon_label}`;
            rsub.textContent = data.keterangan || '';

            _applyVoucherToReceipt(data);
            _updateStopBtnAfterVoucher();
            showToast(`✓ Voucher valid! Diskon ${data.diskon_label}`, 'success');
        })
        .catch(() => {
            btn.disabled = false;
            btn.innerHTML = '<i class="bx bx-search-alt"></i> CEK';
            showToast('✗ Gagal cek voucher', 'error');
        });
}

function _applyVoucherToReceipt(data) {
    document.getElementById('voucher_diskon_row')?.remove();

    const totalRow = document.querySelector('#billing_detail .total-row');
    if (!totalRow) return;

    const diskonRow = document.createElement('div');
    diskonRow.id = 'voucher_diskon_row';
    diskonRow.className = 'voucher-diskon-row';
    diskonRow.innerHTML = `
        <span><i class='bx bx-gift' style="color:var(--success)"></i> Voucher ${escapeHtml(data.kode)}</span>
        <span>- Rp ${data.diskon.toLocaleString('id-ID')}</span>`;
    totalRow.after(diskonRow);

    const totalAmt = totalRow.querySelector('span:last-child');
    if (totalAmt) totalAmt.textContent = 'Rp ' + data.total_setelah.toLocaleString('id-ID');

    stopGrandTotal = data.total_setelah;

    // ── UPDATE tampilan saldo member ──
    const saldoNow = stopMemberSaldo; // saldo sebelum dipotong (dari get_summary)
    const sisaBayar = Math.max(0, data.total_setelah - saldoNow);
    const saldoSetelah = Math.max(0, saldoNow - data.total_setelah);

    // Cari elemen saldo di billing_detail dan update
    const saldoRows = document.querySelectorAll('#billing_detail .billing-item');
    saldoRows.forEach(row => {
        const label = row.querySelector('span:first-child');
        if (!label) return;

        if (label.textContent.includes('Saldo tersedia')) {
            // Update label saldo tersedia — tidak berubah, ini saldo asli
        }
    });

    // Update info box saldo (teks "Saldo cukup" atau "Sisa bayar cash")
    const saldoInfoBox = document.querySelector('#billing_detail [style*="rgba(0,0,0,.25)"]');
    if (saldoInfoBox && saldoNow > 0) {
        const totalAfterVoucher = data.total_setelah;
        if (saldoNow >= totalAfterVoucher) {
            saldoInfoBox.innerHTML = `
                <div style="font-size:.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;font-weight:700;margin-bottom:10px;">
                    <i class='bx bx-wallet'></i> Info Pembayaran Member
                </div>
                <div class="billing-item" style="border-color:rgba(255,255,255,.08);">
                    <span style="color:#94a3b8;font-size:.85rem;">Saldo tersedia</span>
                    <span style="color:var(--success);font-weight:700;">Rp ${saldoNow.toLocaleString('id-ID')}</span>
                </div>
                <div style="text-align:center;margin-top:8px;font-size:.8rem;color:var(--success);font-weight:700;">
                    <i class='bx bx-check-circle'></i> Saldo cukup — otomatis terpotong penuh
                </div>`;
        } else {
            const potong   = saldoNow;
            const sisaCash = totalAfterVoucher - saldoNow;
            saldoInfoBox.innerHTML = `
                <div style="font-size:.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;font-weight:700;margin-bottom:10px;">
                    <i class='bx bx-wallet'></i> Info Pembayaran Member
                </div>
                <div class="billing-item" style="border-color:rgba(255,255,255,.08);">
                    <span style="color:#94a3b8;font-size:.85rem;">Saldo tersedia</span>
                    <span style="color:var(--warning);font-weight:700;">Rp ${potong.toLocaleString('id-ID')}</span>
                </div>
                <div class="billing-item" style="border-color:rgba(255,255,255,.08);">
                    <span style="color:#94a3b8;font-size:.85rem;">Terpotong dari saldo</span>
                    <span style="color:var(--warning);font-weight:700;">- Rp ${potong.toLocaleString('id-ID')}</span>
                </div>
                <div class="billing-item" style="border:none;padding-bottom:0;">
                    <span style="color:#f87171;font-size:.85rem;font-weight:700;">💵 Sisa bayar cash</span>
                    <span style="color:var(--danger);font-weight:800;font-size:1rem;">Rp ${sisaCash.toLocaleString('id-ID')}</span>
                </div>`;
        }
    }
}

function _updateStopBtnAfterVoucher() {
    const btn   = document.getElementById('confirm_stop_btn');
    const lblEl = document.getElementById('stop_btn_label');
    if (!btn || !lblEl) return;
    lblEl.textContent = '🎁 PAKAI VOUCHER & STOP';
    btn.style.background = 'linear-gradient(135deg,var(--success),#059669)';
    btn.style.color = '#fff';
}

// ===================================================================
// STOP MODAL
// ===================================================================
function openStopModal(id, name) {
    currentStopTvId  = id;
    alreadyStopped   = false;
    stopNeedCash     = 0;
    stopMemberSaldo  = 0;
    stopGrandTotal   = 0;
    _stopGrandTotalAsli = 0;
    selectedPaymentMethod = 'Cash';

    document.getElementById('paid_badge_info')?.remove();

    const savedTrxId = stoppedTrxMap[id] || null;
    const timerEl    = document.getElementById(`timer-${id}`);
    currentStopTrxId = savedTrxId || (timerEl ? parseInt(timerEl.dataset.trxId) || null : null);
    if (savedTrxId) alreadyStopped = true;

    const detailBox  = document.getElementById('billing_detail');
    const warningBox = document.getElementById('saldo_warning_box');
    const stopBtn    = document.getElementById('confirm_stop_btn');
    const btnLabel   = document.getElementById('stop_btn_label');

    warningBox.style.display = 'none'; warningBox.innerHTML = '';

    // Reset voucher & payment method
    document.getElementById('voucher_box').style.display = 'none';
    resetVoucherState();
    const pmBox  = document.getElementById('payment_method_box');
    const pmGrid = document.getElementById('payment_method_grid');
    if (pmBox)  pmBox.style.display = 'none';
    if (pmGrid) pmGrid.innerHTML = '';

    stopBtn.style.background = ''; stopBtn.style.color = ''; stopBtn.disabled = true;
    document.getElementById('modalStop').style.display = 'flex';
    detailBox.innerHTML = `<div style="text-align:center;padding:20px;"><i class="bx bx-loader-alt bx-spin" style="font-size:2rem;color:var(--primary);"></i><div style="margin-top:10px;color:var(--text-dim);">Menghitung tagihan...</div></div>`;

    let url = `process.php?action=get_summary&tv_id=${id}`;
    if (currentStopTrxId) url += `&trx_id=${currentStopTrxId}`;

    fetch(url)
        .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.text(); })
        .then(raw => {
            let data;
            try { data = JSON.parse(raw); } catch(e) {
                detailBox.innerHTML = `<div style="color:var(--danger);padding:15px;"><i class='bx bx-error' style="font-size:2rem;"></i><div style="margin-top:8px;font-weight:700;">Response tidak valid</div><pre style="font-size:.65rem;color:#94a3b8;margin-top:10px;background:rgba(0,0,0,.4);padding:10px;border-radius:8px;overflow:auto;max-height:120px;white-space:pre-wrap;">${escapeHtml(raw.substring(0,500))}</pre></div>`;
                stopBtn.disabled = true; return;
            }

            if (data.status !== 'success') {
                if (alreadyStopped) {
                    detailBox.innerHTML = buildMinimalTimeoutView(name);
                    renderPaymentMethods([], 'Cash');
                    document.getElementById('voucher_box').style.display = 'block';
                    btnLabel.textContent = '✓ TUTUP & SELESAI';
                    stopBtn.style.background = 'linear-gradient(135deg,var(--success),#059669)';
                    stopBtn.style.color = '#fff'; stopBtn.disabled = false; return;
                }
                detailBox.innerHTML = `<div style="color:var(--danger);text-align:center;padding:15px;"><i class='bx bx-error' style="font-size:2rem;"></i><div style="margin-top:8px;">${escapeHtml(data.msg || 'Error')}</div></div>`;
                stopBtn.disabled = true; return;
            }

            stopGrandTotal      = data.grand_total  || 0;
            _stopGrandTotalAsli = stopGrandTotal;
            stopMemberSaldo     = data.member_saldo || 0;

            const memberId = data.member_id || null;
            const isMember = memberId && memberId > 0;
            const isOpen   = data.jenis_paket === 'open';
            stopNeedCash   = (isMember && isOpen && stopMemberSaldo < stopGrandTotal)
                ? Math.max(0, stopGrandTotal - stopMemberSaldo) : 0;

            const payStatus = data.payment_status || 'paid';

            // Selalu tampilkan voucher box
            document.getElementById('voucher_box').style.display = 'block';

// Tampilkan payment method
if (alreadyStopped) {
    // Expired timeout: tampilkan metode pembayaran untuk konfirmasi
    renderPaymentMethods(data.metode_aktif || [], data.payment_method);
} else if (payStatus === 'paid') {
    // Sudah bayar di awal: tetap render grid dengan pre-select metode awal
    renderPaymentMethods(data.metode_aktif || [], data.payment_method || 'Cash');
    // Tambah badge info di atas grid
    const paidBadge = document.createElement('div');
    paidBadge.id = 'paid_badge_info';
    paidBadge.style.cssText = `background:rgba(16,185,129,0.07);border:1px solid rgba(16,185,129,0.25);border-radius:14px;padding:10px 16px;margin-bottom:10px;display:flex;align-items:center;gap:10px;font-size:.82rem;font-weight:700;color:var(--success);`;
    paidBadge.innerHTML = `<i class='bx bx-check-shield' style="font-size:1.2rem;flex-shrink:0;"></i><div><div>Dibayar di awal — konfirmasi atau ganti metode jika perlu</div><div style="font-size:.72rem;color:var(--text-dim);font-weight:500;margin-top:2px;">Metode tersimpan: <b style="color:#fff;">${escapeHtml(data.payment_method || 'Cash')}</b></div></div>`;
    const pmBoxEl = document.getElementById('payment_method_box');
    if (pmBoxEl) pmBoxEl.parentNode.insertBefore(paidBadge, pmBoxEl);
} else {
    renderPaymentMethods(data.metode_aktif || [], data.payment_method);
}

            // Build receipt HTML
            let fnbHtml = '';
            (data.fnb_list || []).forEach(i => {
                fnbHtml += `<div class="billing-item"><span><i class='bx bx-food-menu' style="color:var(--primary);"></i> ${escapeHtml(i.nama_barang)} (x${i.qty})</span><span>Rp ${parseInt(i.total_harga).toLocaleString('id-ID')}</span></div>`;
            });

            let calcHtml = '';
            if (data.jenis_paket === 'open' && data.calc_info) {
                const ci = data.calc_info;
                calcHtml = `<div style="background:rgba(6,182,212,0.1);padding:12px;border-radius:12px;margin-top:12px;font-size:.75rem;border:1px solid rgba(6,182,212,0.2);"><div style="color:var(--primary);font-weight:700;margin-bottom:8px;"><i class='bx bx-calculator'></i> Detail Perhitungan:</div><div style="display:flex;justify-content:space-between;color:var(--text-dim);margin-bottom:4px;"><span>Rate:</span><span>Rp ${ci.rate_per_jam.toLocaleString('id-ID')}/jam</span></div><div style="display:flex;justify-content:space-between;color:var(--text-dim);margin-bottom:4px;"><span>Biaya aktual:</span><span>Rp ${ci.biaya_mentah.toLocaleString('id-ID')}</span></div><div style="display:flex;justify-content:space-between;color:var(--text-dim);"><span>Min. charge:</span><span>Rp ${ci.min_charge.toLocaleString('id-ID')}</span></div></div>`;
            }

            let saldoRow = '';
            if (isMember && isOpen) {
                const cukup = stopMemberSaldo >= stopGrandTotal;
                saldoRow = buildSaldoRow(cukup, stopMemberSaldo, stopGrandTotal, stopNeedCash);
            } else if (isMember && !isOpen) {
                const n = Math.max(0, stopGrandTotal - stopMemberSaldo);
                stopNeedCash = n;
                saldoRow = buildSaldoRow(n === 0, stopMemberSaldo, stopGrandTotal, n);
            }

            let timeoutBadge = '';
            if (alreadyStopped) {
                timeoutBadge = `<div style="background:rgba(245,158,11,0.1);border:1px solid rgba(245,158,11,0.25);border-radius:12px;padding:12px;margin-bottom:16px;text-align:center;"><div style="display:flex;align-items:center;justify-content:center;gap:8px;"><i class='bx bx-time-five' style="font-size:1.2rem;color:var(--warning);"></i><span style="color:var(--warning);font-weight:800;font-size:.85rem;">SESI BERAKHIR OTOMATIS</span></div><div style="font-size:.75rem;color:var(--text-dim);margin-top:6px;">Timer habis — sistem telah menghentikan sesi ini. Berikut rincian tagihannya:</div></div>`;
            }

            detailBox.innerHTML = `${timeoutBadge}<div style="text-align:center;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:15px;margin-bottom:15px;"><h2 style="margin:0;color:var(--primary);font-family:'Orbitron',sans-serif;font-size:1.3rem;">${escapeHtml(name)}</h2><div style="color:var(--text-dim);font-size:.85rem;margin-top:5px;"><i class='bx bx-user'></i> ${escapeHtml(data.player)}</div></div><div class="billing-item"><b><i class='bx bx-time-five' style="color:var(--primary);"></i> Sesi (${escapeHtml(data.paket)})</b><span style="font-weight:700;">Rp ${data.biaya_sesi.toLocaleString('id-ID')}</span></div><div style="font-size:.75rem;color:var(--text-dim);padding-left:20px;margin-bottom:6px;">Durasi: ${escapeHtml(data.durasi)}</div>${calcHtml}${fnbHtml.length ? `<div style="margin-top:12px;font-weight:bold;color:var(--primary);font-size:.75rem;margin-bottom:4px;"><i class='bx bx-food-menu'></i> FnB ORDERS:</div>${fnbHtml}` : ''}<div class="total-row"><span><i class='bx bx-wallet'></i> GRAND TOTAL</span><span>Rp ${stopGrandTotal.toLocaleString('id-ID')}</span></div>${saldoRow}`;

            if (isMember && stopNeedCash > 0 && !alreadyStopped) {
                warningBox.style.display = 'block';
                warningBox.innerHTML = `<div style="background:linear-gradient(135deg,rgba(239,68,68,0.15),rgba(239,68,68,0.08));border:1px solid rgba(239,68,68,0.4);border-radius:16px;padding:16px 18px;animation:warningPulse 2s ease-in-out infinite;"><div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;"><div style="width:42px;height:42px;background:rgba(239,68,68,.2);border-radius:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0;"><i class='bx bxs-error-alt' style="font-size:1.5rem;color:var(--danger);"></i></div><div><div style="font-weight:800;color:#f87171;font-size:.95rem;">⚠ Saldo Tidak Mencukupi!</div><div style="font-size:.72rem;color:#94a3b8;margin-top:2px;">Minta pelanggan siapkan uang cash</div></div></div><div style="background:rgba(0,0,0,.3);border-radius:10px;padding:12px 14px;"><div style="display:flex;justify-content:space-between;font-size:.8rem;color:#94a3b8;margin-bottom:5px;"><span>Total tagihan</span><span style="color:#fff;font-weight:700;">Rp ${stopGrandTotal.toLocaleString('id-ID')}</span></div><div style="display:flex;justify-content:space-between;font-size:.8rem;color:#94a3b8;margin-bottom:8px;"><span>Dipotong saldo</span><span style="color:var(--warning);font-weight:700;">- Rp ${stopMemberSaldo.toLocaleString('id-ID')}</span></div><div style="height:1px;background:rgba(239,68,68,.25);margin-bottom:8px;"></div><div style="display:flex;justify-content:space-between;align-items:center;"><span style="font-size:.85rem;font-weight:800;color:#f87171;">💵 KURANG CASH</span><span style="font-size:1.25rem;font-weight:900;color:var(--danger);font-family:'JetBrains Mono',monospace;">Rp ${stopNeedCash.toLocaleString('id-ID')}</span></div></div></div>`;
            }

            // Label tombol stop
            if (alreadyStopped) {
                btnLabel.textContent = '✓ TUTUP & SELESAI';
                stopBtn.style.background = 'linear-gradient(135deg,var(--success),#059669)';
                stopBtn.style.color = '#fff';
            } else if (payStatus === 'paid') {
                btnLabel.textContent = '⏹ STOP SESI';
                stopBtn.style.background = 'linear-gradient(135deg,var(--success),#059669)';
                stopBtn.style.color = '#fff';
            } else if (isMember && stopNeedCash > 0) {
                stopBtn.style.background = 'linear-gradient(135deg,#f59e0b,#d97706)';
                stopBtn.style.color = '#000';
                btnLabel.textContent = 'KONFIRMASI & STOP';
            } else if (isMember) {
                stopBtn.style.background = 'linear-gradient(135deg,var(--success),#059669)';
                stopBtn.style.color = '#fff';
                btnLabel.textContent = '✓ POTONG SALDO & STOP';
            } else {
                stopBtn.style.background = '';
                stopBtn.style.color = '';
                btnLabel.textContent = 'BAYAR & STOP';
            }
            stopBtn.disabled = false;
        })
        .catch(() => {
            if (alreadyStopped) {
                detailBox.innerHTML = buildMinimalTimeoutView(name);
                document.getElementById('voucher_box').style.display = 'block';
                btnLabel.textContent = '✓ TUTUP & SELESAI';
                stopBtn.style.background = 'linear-gradient(135deg,var(--success),#059669)';
                stopBtn.style.color = '#fff'; stopBtn.disabled = false; return;
            }
            detailBox.innerHTML = `<div style="color:var(--danger);text-align:center;padding:15px;"><i class='bx bx-error' style="font-size:2rem;"></i><div style="margin-top:8px;">Gagal memuat data tagihan</div></div>`;
            stopBtn.disabled = true;
        });
}

function buildMinimalTimeoutView(name) {
    return `<div style="text-align:center;border-bottom:1px solid rgba(255,255,255,.1);padding-bottom:15px;margin-bottom:15px;"><h2 style="margin:0;color:var(--primary);font-family:'Orbitron',sans-serif;font-size:1.3rem;">${escapeHtml(name)}</h2><div style="color:var(--text-dim);font-size:.85rem;margin-top:5px;"><i class='bx bx-time'></i> Sesi berakhir otomatis</div></div><div style="background:rgba(16,185,129,.08);border:1px solid rgba(16,185,129,.2);border-radius:14px;padding:16px;text-align:center;"><div style="font-size:1.5rem;margin-bottom:8px;">✅</div><div style="color:#34d399;font-weight:800;font-size:.95rem;">Sesi Sudah Selesai</div><div style="color:#94a3b8;font-size:.8rem;margin-top:6px;">Timer habis — sesi dihentikan otomatis oleh sistem.<br>Klik <b style="color:#fff;">Tutup & Selesai</b> untuk konfirmasi.</div></div>`;
}

function buildSaldoRow(cukup, saldo, total, needCash) {
    return `<div style="margin-top:14px;background:rgba(0,0,0,.25);border-radius:12px;padding:12px;border:1px solid rgba(255,255,255,.06);"><div style="font-size:.7rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;font-weight:700;margin-bottom:10px;"><i class='bx bx-wallet'></i> Info Pembayaran Member</div><div class="billing-item" style="border-color:rgba(255,255,255,.08);"><span style="color:#94a3b8;font-size:.85rem;">Saldo tersedia</span><span style="color:${cukup ? 'var(--success)' : 'var(--warning)'};font-weight:700;">Rp ${saldo.toLocaleString('id-ID')}</span></div>${!cukup ? `<div class="billing-item" style="border-color:rgba(255,255,255,.08);"><span style="color:#94a3b8;font-size:.85rem;">Terpotong dari saldo</span><span style="color:var(--warning);font-weight:700;">- Rp ${saldo.toLocaleString('id-ID')}</span></div><div class="billing-item" style="border:none;padding-bottom:0;"><span style="color:#f87171;font-size:.85rem;font-weight:700;">💵 Sisa bayar cash</span><span style="color:var(--danger);font-weight:800;font-size:1rem;">Rp ${needCash.toLocaleString('id-ID')}</span></div>` : `<div style="text-align:center;margin-top:8px;font-size:.8rem;color:var(--success);font-weight:700;"><i class='bx bx-check-circle'></i> Saldo cukup — otomatis terpotong penuh</div>`}</div>`;
}

// ===================================================================
// CONFIRM STOP
// ===================================================================
function confirmStop() {
    if (!currentStopTvId) return;

    const doStop = () => {
        const btn = document.getElementById('confirm_stop_btn');
        if (!btn) return;
        btn.disabled = true;
        btn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Memproses...';

if (alreadyStopped) {
    // Update payment method dulu ke DB
    const updateFd = new FormData();
    updateFd.append('action', 'update_payment_method');
    updateFd.append('tv_id', currentStopTvId);
    updateFd.append('payment_method', selectedPaymentMethod);
    updateFd.append('payment_status', 'paid');
    updateFd.append('ajax', '1');
    if (currentStopTrxId) updateFd.append('trx_id', currentStopTrxId);

    const promises = [fetch('process.php', { method: 'POST', body: updateFd })];

    // Jika ada voucher, proses sekalian
    if (_voucherId && currentStopTrxId) {
        const vfd = new FormData();
        vfd.append('action',         'apply_voucher_to_completed');
        vfd.append('voucher_id',     _voucherId);
        vfd.append('voucher_diskon', _voucherDiskon);
        vfd.append('trx_id',         currentStopTrxId);
        promises.push(fetch('process.php', { method: 'POST', body: vfd }));
    }

    Promise.all(promises)
        .catch(() => {})
        .finally(() => {
            _removeExpiredFromStorage(currentStopTvId);
            delete stoppedTrxMap[currentStopTvId];
            delete pendingTimeouts[currentStopTvId];
            window.location.href = 'index.php';
        });
    return;
}
const fd = new FormData();
        fd.append('action', 'stop');
        fd.append('tv_id', currentStopTvId);
        fd.append('ajax', '1');
        if (currentStopTrxId) fd.append('trx_id', currentStopTrxId);
        fd.append('payment_method', selectedPaymentMethod);
        // ── Kirim voucher sekalian ke action stop ──
        if (_voucherId && _voucherDiskon > 0) {
            fd.append('voucher_id',     _voucherId);
            fd.append('voucher_diskon', _voucherDiskon);
        }
fetch('process.php', { method: 'POST', body: fd })
    .then(r => r.json())
    .then(data => {
        if (data.status === 'error') { /* ... */ return; }

if (_voucherId) {
            const vfd = new FormData();

            if (alreadyStopped) {
                // Auto-timeout: sesi sudah di-stop server tanpa voucher,
                // perlu apply diskon + kembalikan saldo member
                vfd.append('action',         'apply_voucher_to_completed');
                vfd.append('voucher_id',     _voucherId);
                vfd.append('voucher_diskon', _voucherDiskon);
                vfd.append('trx_id',         data.trx_id || currentStopTrxId || 0);
            } else {
                // Normal stop: voucher_id & voucher_diskon sudah dikirim
                // ke action=stop dan diproses server (total_bayar & saldo sudah benar),
                // tinggal tandai voucher sebagai 'used'
                vfd.append('action',     'apply_voucher');
                vfd.append('voucher_id', _voucherId);
                vfd.append('trx_id',     data.trx_id || currentStopTrxId || 0);
            }

            fetch('process.php', { method: 'POST', body: vfd }).catch(() => {});
        }

        _removeExpiredFromStorage(currentStopTvId);
        delete pendingTimeouts[currentStopTvId];
        refreshPlugAfterStop(currentStopTvId);
        window.location.href = 'index.php';
    })
            .catch(err => {
                console.error('Stop error:', err);
                showToast('⚠ Koneksi bermasalah, mencoba ulang...', 'warning');
                setTimeout(() => {
                    _removeExpiredFromStorage(currentStopTvId);
                    delete pendingTimeouts[currentStopTvId];
                    let url = `process.php?action=stop&tv_id=${currentStopTvId}`;
                    if (currentStopTrxId) url += `&trx_id=${currentStopTrxId}`;
                    window.location.href = url;
                }, 1500);
            });
    };

    if (alreadyStopped) { doStop(); return; }

    if (stopNeedCash > 0) {
        Swal.fire({
            title: '⚠ Konfirmasi Pembayaran Cash',
            html: `<div style="text-align:left;font-size:.9rem;color:#94a3b8;line-height:1.8;">Saldo member tidak mencukupi.<br><div style="background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.2);border-radius:12px;padding:14px;margin-top:12px;"><div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span>Total tagihan:</span><b style="color:#fff;">Rp ${stopGrandTotal.toLocaleString('id-ID')}</b></div><div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span>Dipotong saldo:</span><b style="color:#fbbf24;">- Rp ${stopMemberSaldo.toLocaleString('id-ID')}</b></div><div style="display:flex;justify-content:space-between;border-top:1px solid rgba(255,255,255,.1);padding-top:8px;margin-top:4px;"><span style="font-weight:800;color:#f87171;">💵 Bayar cash:</span><b style="color:#ef4444;font-size:1.1rem;">Rp ${stopNeedCash.toLocaleString('id-ID')}</b></div></div><div style="margin-top:12px;color:#fbbf24;font-weight:700;font-size:.85rem;"><i class='bx bx-check-circle'></i> Pastikan uang cash sudah diterima sebelum klik konfirmasi.</div></div>`,
            icon: null, showCancelButton: true,
            confirmButtonText: '<i class="bx bx-check"></i> Sudah Terima Cash — STOP',
            cancelButtonText: 'Batal', confirmButtonColor: '#ef4444', cancelButtonColor: '#334155',
            background: '#1e293b', color: '#fff',
        }).then(result => { if (result.isConfirmed) doStop(); });
    } else {
        doStop();
    }
}

// ===================================================================
// FNB & MODALS
// ===================================================================
function openFnBModal(id, name) {
    document.getElementById('fnb_tv_id').value = id;
    document.getElementById('fnb_title').innerHTML = `<i class='bx bx-food-menu'></i> FnB: ${name}`;
    const old = document.getElementById('fnb_form').querySelector('[name="bypass_saldo"]');
    if (old) old.remove();
    document.getElementById('modalFnB').style.display = 'flex';
}

function openAddTimeModal(tvId, tvName) {
    document.getElementById('addtime_tv_id').value = tvId;
    document.getElementById('addtime_tv_label').innerHTML = `<i class='bx bx-tv'></i> <b>${escapeHtml(tvName)}</b>`;
    document.getElementById('modalAddTime').style.display = 'flex';
    const btn = document.getElementById('addtime_submit_btn');
    btn.disabled = false; btn.innerHTML = '<i class="bx bx-plus"></i> TAMBAH WAKTU';
    updateAddTimePreview();
}

function updateAddTimePreview() {
    const sel = document.getElementById('addtime_paket_id');
    const opt = sel.options[sel.selectedIndex];
    if (opt) {
        document.getElementById('addtime_preview_menit').textContent = `+${opt.dataset.menit||0} menit`;
        document.getElementById('addtime_preview_harga').textContent = `+Rp ${parseInt(opt.dataset.harga||0).toLocaleString('id-ID')}`;
    }
}

function confirmAddTime() {
    const tvId    = document.getElementById('addtime_tv_id').value;
    const paketId = document.getElementById('addtime_paket_id').value;
    const btn     = document.getElementById('addtime_submit_btn');
    if (!tvId || !paketId) return;
    btn.disabled = true; btn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Memproses...';
    const fd = new FormData();
    fd.append('action', 'add_time'); fd.append('tv_id', tvId); fd.append('paket_id', paketId);
    fetch('process.php', { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            if (data.status === 'success') {
                showToast(`✓ ${data.nama_paket}: +${data.tambah_menit}m ditambahkan!`, 'success');
                const timerObj = timers.find(t => String(t.id) === String(tvId));
                if (timerObj) {
                    const isPaused = timerObj.el.dataset.status === 'paused';
                    if (isPaused) { timerObj.initSec = data.new_sisa_detik; timerObj.el.innerHTML = formatTime(data.new_sisa_detik); }
                    else { timerObj.initSec = data.new_sisa_detik; timerObj.startTime = Date.now(); timerObj.done = false; timerObj.el.classList.remove('time-expired'); timerObj.el.dataset.expired = '0'; timerObj.el.dataset.status = 'active'; const card = document.getElementById(`card-${tvId}`); if (card) { card.classList.remove('available'); card.classList.add('active'); const pill = card.querySelector('.status-pill'); if (pill) pill.innerHTML = "<i class='bx bx-play-circle'></i> ACTIVE"; } }
                }
                document.getElementById(`timeout-notif-${tvId}`)?.remove();
                delete stoppedTrxMap[tvId];
                _removeExpiredFromStorage(tvId);
                closeModal('modalAddTime');
            } else { showToast(`✗ ${data.msg || 'Gagal tambah waktu'}`, 'error'); btn.disabled = false; btn.innerHTML = '<i class="bx bx-plus"></i> TAMBAH WAKTU'; }
        })
        .catch(() => { showToast('✗ Gagal tambah waktu', 'error'); btn.disabled = false; btn.innerHTML = '<i class="bx bx-plus"></i> TAMBAH WAKTU'; });
}

function closeModal(id) {
    const el = document.getElementById(id);
    if (el) el.style.display = 'none';
    if (id === 'modalStop') {
        if (currentStopTvId && alreadyStopped) {
            const card    = document.getElementById(`card-${currentStopTvId}`);
            const timerEl = document.getElementById(`timer-${currentStopTvId}`);
            if (card) { card.classList.remove('active', 'paused'); card.classList.add('available'); const pill = card.querySelector('.status-pill'); if (pill) pill.innerHTML = "<i class='bx bx-circle'></i> AVAILABLE"; }
            if (timerEl) { timerEl.innerText = '00:00:00'; timerEl.classList.remove('time-expired'); timerEl.dataset.status = 'available'; }
        }
        setTimeout(() => {
            const next = Object.entries(expiredQueue);
            if (next.length > 0) {
                const [tvId, trxId] = next[0]; delete expiredQueue[tvId];
                const card2   = document.getElementById(`card-${tvId}`);
                const tvTitle = card2?.querySelector('.tv-title')?.textContent || `TV ${tvId}`;
                if (_belumBayarTvIds[tvId]) {
                    fetch(`process.php?action=stop&tv_id=${tvId}&trx_id=${trxId}&ajax=1&auto_timeout=1`)
                        .then(r => r.json())
                        .then(data => {
                            stoppedTrxMap[tvId] = data.trx_id || trxId;
                            _saveExpiredToStorage(tvId, data.trx_id || trxId);
                            refreshPlugAfterStop(tvId);
                            _showBelumBayarExpiredModal(tvId, data.trx_id || trxId);
                        })
                        .catch(() => {
                            stoppedTrxMap[tvId] = trxId;
                            _saveExpiredToStorage(tvId, trxId);
                            refreshPlugAfterStop(tvId);
                            _showBelumBayarExpiredModal(tvId, trxId);
                        });
                } else {
                    fetch(`process.php?action=stop&tv_id=${tvId}&trx_id=${trxId}&ajax=1&auto_timeout=1`)
                        .then(r => r.json())
                        .then(data => {
                            stoppedTrxMap[tvId] = data.trx_id || trxId;
                            _saveExpiredToStorage(tvId, data.trx_id || trxId);
                            _showExpiredNotif(tvId);
                            refreshPlugAfterStop(tvId);
                            openStopModal(tvId, tvTitle);
                        })
                        .catch(() => {
                            stoppedTrxMap[tvId] = trxId;
                            _saveExpiredToStorage(tvId, trxId);
                            _showExpiredNotif(tvId);
                            refreshPlugAfterStop(tvId);
                            openStopModal(tvId, tvTitle);
                        });
                }
            }
        }, 600);
    }
}

function openMoveModal(id) {
    document.getElementById('move_from').value = id;
    document.getElementById('modalMove').style.display = 'flex';
}

// ===================================================================
// REMOTE
// ===================================================================
function remote(e, act, id) {
    e.preventDefault();
    const btn = e.currentTarget;
    btn.style.transform = 'scale(0.9)';
    setTimeout(() => { if (btn) btn.style.transform = ''; }, 150);
    fetch(`process.php?action=${act}&tv_id=${id}&ajax=1`).catch(() => {});
}

function connectTV(e, tvId, tvName) {
    e.preventDefault();
    const btn = e.currentTarget, icon = btn.querySelector('i');
    btn.classList.add('connecting'); icon.className = 'bx bx-loader-alt';
    fetch(`process.php?action=adb_connect&tv_id=${tvId}&ajax=1`)
        .then(() => setTimeout(() => {
            fetch('process.php?action=check_adb_status').then(r => r.json()).then(d => {
                btn.classList.remove('connecting');
                const online = d.tvs?.find(t => t.id == tvId)?.status === 'online';
                if (online) { icon.className='bx bx-check'; btn.style.background='var(--success)'; btn.style.color='#000'; showToast(`✓ ${tvName} Connected!`,'success'); }
                else { icon.className='bx bx-x'; btn.style.background='var(--danger)'; btn.style.color='#fff'; showToast(`✗ ${tvName} gagal connect`,'error'); }
                d.tvs?.forEach(tv => { const el = document.getElementById(`adb-status-${tv.id}`); if (el) { el.classList.remove('online','offline'); el.classList.add(tv.status); const t=el.querySelector('.adb-text'); if(t) t.textContent=`ADB ${tv.status.toUpperCase()}`; } });
                setTimeout(() => { icon.className='bx bx-link'; btn.style.background=''; btn.style.color=''; }, 2000);
            }).catch(() => { btn.classList.remove('connecting'); icon.className='bx bx-error'; btn.style.background='var(--warning)'; showToast(`⚠ Tidak dapat verifikasi ${tvName}`,'warning'); setTimeout(()=>{icon.className='bx bx-link';btn.style.background='';},2000); });
        }, 2000))
        .catch(() => { btn.classList.remove('connecting'); icon.className='bx bx-error'; btn.style.background='var(--danger)'; btn.style.color='#fff'; showToast(`✗ Gagal connect ${tvName}`,'error'); setTimeout(()=>{icon.className='bx bx-link';btn.style.background='';btn.style.color='';},2000); });
}

function refreshADBStatus() {
    fetch('process.php?action=check_adb_status').then(r => r.json()).then(data => {
        data.tvs?.forEach(tv => { const el = document.getElementById(`adb-status-${tv.id}`); if (el) { el.classList.remove('online','offline'); el.classList.add(tv.status); const t=el.querySelector('.adb-text'); if(t) t.textContent=`ADB ${tv.status.toUpperCase()}`; } });
    }).catch(() => {});
}

// ===================================================================
// TUYA PLUG
// ===================================================================
function setPlugBadge(tvId, state, power) {
    const btn = document.getElementById('plug-btn-' + tvId);
    const dot = document.getElementById('plug-dot-' + tvId);
    const txt = document.getElementById('plug-text-' + tvId);
    if (!btn) return;
    btn.dataset.plugState = state;
    btn.classList.remove('plug-on', 'plug-off', 'plug-loading');
    if (state === 'on') {
        btn.classList.add('plug-on');
        if (dot) { dot.style.background = 'var(--success)'; dot.style.boxShadow = '0 0 6px var(--success)'; }
        const pwrTxt = (power != null && power > 0) ? ` (${power}W)` : '';
        if (txt) txt.textContent = '⚡ PLUG ON' + pwrTxt;
    } else if (state === 'off') {
        btn.classList.add('plug-off');
        if (dot) { dot.style.background = 'var(--danger)'; dot.style.boxShadow = 'none'; }
        if (txt) txt.textContent = '⚡ PLUG OFF';
    } else {
        btn.classList.add('plug-loading');
        if (dot) { dot.style.background = '#475569'; dot.style.boxShadow = 'none'; }
        if (txt) txt.textContent = '⚡ PLUG —';
    }
}

function refreshPlugStatus(tvId, plugIds, retry) {
    if (!plugIds || plugIds.length === 0) return;
    fetch('tuya_api.php?action=tv_plug_status&tv_id=' + tvId)
        .then(r => r.json())
        .then(d => {
            if (d.status === 'ok') { setPlugBadge(tvId, d.is_on ? 'on' : 'off', d.power); }
            else { setPlugBadge(tvId, 'err', null); if (retry !== false) setTimeout(() => refreshPlugStatus(tvId, plugIds, false), 4000); }
        })
        .catch(() => { setPlugBadge(tvId, 'err', null); if (retry !== false) setTimeout(() => refreshPlugStatus(tvId, plugIds, false), 4000); });
}

function togglePlugFromDashboard(tvId) {
    const btn = document.getElementById('plug-btn-' + tvId);
    if (!btn) return;
    const currentState = btn.dataset.plugState || 'unknown';
    const plugIds = JSON.parse(btn.dataset.plugIds || '[]');
    if (currentState === 'unknown' || currentState === 'err') { showToast('⚡ Mengambil status plug...', 'info'); refreshPlugStatus(tvId, plugIds, false); return; }
    const isCurrentlyOn = (currentState === 'on');
    const newState      = isCurrentlyOn ? '0' : '1';
    setPlugBadge(tvId, newState === '1' ? 'on' : 'off', null);
    btn.style.pointerEvents = 'none';
    const fd = new FormData();
    fd.append('action', 'tv_plug_toggle'); fd.append('tv_id', tvId); fd.append('state', newState);
    fetch('tuya_api.php', { method: 'POST', body: fd })
        .then(r => r.json())
        .then(d => {
            btn.style.pointerEvents = '';
            if (d.status !== 'ok') { setPlugBadge(tvId, isCurrentlyOn ? 'on' : 'off', null); showToast('⚡ Gagal toggle plug' + (d.msg ? ': ' + d.msg : ''), 'error'); }
            else { setPlugBadge(tvId, d.is_on ? 'on' : 'off', d.power); showToast('⚡ Plug ' + (d.is_on ? 'ON' : 'OFF'), d.is_on ? 'success' : 'info'); }
        })
        .catch(() => { btn.style.pointerEvents = ''; setPlugBadge(tvId, isCurrentlyOn ? 'on' : 'off', null); showToast('⚡ Tidak bisa hubungi plug', 'error'); });
}

function refreshPlugAfterStop(tvId) {
    const btn = document.getElementById('plug-btn-' + tvId);
    if (!btn) return;
    const plugIds = JSON.parse(btn.dataset.plugIds || '[]');
    if (plugIds.length === 0) return;
    setTimeout(() => refreshPlugStatus(tvId, plugIds, true), 2000);
}

function initPlugRefresh() {
    document.querySelectorAll('[data-plug-ids]').forEach(el => {
        const tvId    = el.dataset.tvId;
        const plugIds = JSON.parse(el.dataset.plugIds || '[]');
        if (tvId && plugIds.length > 0) {
            const delay = parseInt(tvId) * 300;
            setTimeout(() => refreshPlugStatus(tvId, plugIds, true), delay);
        }
    });
}

function scheduledPlugRefresh() {
    document.querySelectorAll('[data-plug-ids]').forEach(el => {
        const tvId    = el.dataset.tvId;
        const plugIds = JSON.parse(el.dataset.plugIds || '[]');
        if (!tvId || plugIds.length === 0) return;
        // Selalu cek ke server, jangan asumsi
        refreshPlugStatus(tvId, plugIds, false);
    });
}

// Langsung refresh saat halaman siap
document.addEventListener('DOMContentLoaded', () => {
    initPlugRefresh();
    setInterval(scheduledPlugRefresh, 30000);
});

// ===================================================================
// TOAST
// ===================================================================
function showToast(message, type = 'info') {
    document.querySelectorAll('.toast-notification').forEach(t => t.remove());
    const toast = document.createElement('div');
    toast.className = 'toast-notification';
    const colors = { success: ['var(--success)','#000'], error: ['var(--danger)','#fff'], warning: ['var(--warning)','#000'] };
    const [bg, fg] = colors[type] || ['var(--primary)','#000'];
    toast.style.cssText = `position:fixed;bottom:20px;right:20px;background:${bg};color:${fg};padding:15px 25px;border-radius:12px;font-weight:700;font-size:.9rem;z-index:99999;animation:slideIn .3s ease;box-shadow:0 10px 30px rgba(0,0,0,.3);`;
    toast.textContent = message;
    document.body.appendChild(toast);
    setTimeout(() => { toast.style.animation='slideOut .3s ease'; setTimeout(()=>toast.remove(),300); }, 3000);
}

// ===================================================================
// ADB AUTO-RECONNECT
// ===================================================================
const allTVs = [];
document.querySelectorAll('.connect-btn').forEach(btn => allTVs.push({ id: btn.id.replace('connect-btn-',''), ip: btn.dataset.ip }));

function autoReconnectAll() {
    Promise.all(allTVs.map(tv => tv.ip
        ? fetch(`process.php?action=adb_connect&tv_id=${tv.id}&ajax=1`).then(()=>({id:tv.id,success:true})).catch(()=>({id:tv.id,success:false}))
        : Promise.resolve({id:tv.id,success:false})
    )).then(() => setTimeout(refreshADBStatus, 2000));
}

document.addEventListener('click', e => {
    if (!e.target.closest('.stik-selector')) {
        document.querySelectorAll('.stik-selector.active').forEach(s => s.classList.remove('active'));
    }
    if (e.target.classList.contains('modal-backdrop')) {
        const clickedInsideContent = e.composedPath().some(el => el.classList && el.classList.contains('modal-content'));
        if (!clickedInsideContent) e.target.style.display = 'none';
    }
});

setTimeout(autoReconnectAll, 20000);
setInterval(autoReconnectAll, 120000);
setTimeout(refreshADBStatus, 25000);
setInterval(refreshADBStatus, 60000);

initTimers();

function syncReport() { fetch('sync_report.php').then(r=>r.json()).then(d=>{if(d.status==='success')console.log('Synced:',d.server_response);}).catch(()=>{}); }
setTimeout(syncReport, 40000);
setInterval(syncReport, 300000);

// ===================================================================
// [FIX] VISIBILITY CHANGE — cek sessionStorage sebelum reload
// ===================================================================
document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        location._hidden = true;
    }
    if (!document.hidden && location._hidden) {
        location._hidden = false;

        // Cek apakah ada expired pending di sessionStorage
        let stored = [];
        try { stored = JSON.parse(sessionStorage.getItem('_expired_pending') || '[]'); } catch(e) {}

        if (stored.length > 0) {
            // Restore state langsung TANPA reload
            stored.forEach(function(item) {
                const timerEl = document.getElementById('timer-' + item.tv_id);
                const card    = document.getElementById('card-' + item.tv_id);
                if (timerEl) {
                    timerEl.classList.add('time-expired');
                    timerEl.innerHTML = 'WAKTU HABIS';
                    timerEl.dataset.status = 'active';
                }
                if (card) {
                    card.classList.remove('available');
                    card.classList.add('active');
                }
                // Sudah di-stop server, tinggal tunggu konfirmasi
                stoppedTrxMap[item.tv_id] = item.trx_id;
                expiredQueue[item.tv_id]   = item.trx_id;
            });
            setTimeout(processExpiredQueue, 500);
        } else {
            // Tidak ada pending — aman untuk reload
            location.reload();
        }
    }
});

// ===================================================================
// ALUR START SESSION
// ===================================================================
let _pendingStartTvId    = null;
let _pendingStartType    = null;
let _selectedStartMetode = null;

const METODE_LIST = [
    { nama: 'Cash',     icon: 'bx-money',      color: '#10b981' },
    { nama: 'QRIS',     icon: 'bx-qr-scan',    color: '#06b6d4' },
    { nama: 'Transfer', icon: 'bx-transfer',    color: '#8b5cf6' },
    { nama: 'Debit',    icon: 'bx-credit-card', color: '#f59e0b' },
];

function renderMetodeGrid(containerId, selectedCallback) {
    const grid = document.getElementById(containerId);
    if (!grid) return;
    grid.innerHTML = '';
    METODE_LIST.forEach((m, idx) => {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'pay-method-btn' + (idx === 0 ? ' selected' : '');
        btn.dataset.method = m.nama;
        btn.innerHTML = `<i class='bx ${m.icon}' style="font-size:1.6rem;color:${m.color};"></i><span>${m.nama}</span>`;
        btn.style.padding = '16px 8px';
        btn.addEventListener('click', function() {
            grid.querySelectorAll('.pay-method-btn').forEach(b => b.classList.remove('selected'));
            this.classList.add('selected');
            selectedCallback(this.dataset.method);
        });
        grid.appendChild(btn);
        if (idx === 0) selectedCallback(m.nama);
    });
}

function interceptStartSession(e, tvId, type) {
    e.preventDefault();
    _pendingStartTvId    = tvId;
    _pendingStartType    = type;
    _selectedStartMetode = METODE_LIST[0].nama;

    // ── Cek apakah member dipilih ──
    const formId = type === 'fixed' ? `form-fixed-${tvId}` : `form-open-${tvId}`;
    const form   = document.getElementById(formId);
    const memberSelect = form?.querySelector('select[name="member_id"]');
    const memberId     = memberSelect ? memberSelect.value : '';

    // Kalau ada member → skip modal konfirmasi, langsung start dengan metode "Member Saldo"
    if (memberId) {
        _selectedStartMetode = 'Member Saldo';
        _submitStartForm(false, 'Member Saldo');
        return;
    }

    // Kalau tamu → tampilkan modal konfirmasi seperti biasa
    const card   = document.getElementById('card-' + tvId);
    const tvName = card?.querySelector('.tv-title')?.textContent || 'TV ' + tvId;
    let paketText = '';
    if (type === 'fixed') {
        const sel = document.querySelector(`#form-fixed-${tvId} select[name="paket_id"]`);
        paketText = sel?.options[sel.selectedIndex]?.text || 'Paket';
    } else {
        const rateInput = document.querySelector(`#form-open-${tvId} input[name="harga_per_jam"]`);
        paketText = 'Open Bill — Rp ' + (parseInt(rateInput?.value || 0)).toLocaleString('id-ID') + '/jam';
    }

    document.getElementById('startpay_tvname').textContent = tvName;
    document.getElementById('startpay_paket').textContent  = paketText;
    document.getElementById('modalStartPayCheck').style.display = 'flex';
}

function pilihSudahBayar() {
    document.getElementById('modalStartPayCheck').style.display = 'none';
    renderMetodeGrid('metode_pilih_grid', (m) => { _selectedStartMetode = m; });
    document.getElementById('modalPilihMetode').style.display = 'flex';
}

function kembaliKePayCheck() {
    document.getElementById('modalPilihMetode').style.display = 'none';
    document.getElementById('modalStartPayCheck').style.display = 'flex';
}

function konfirmasiStartDenganMetode() {
    document.getElementById('modalPilihMetode').style.display = 'none';
    _submitStartForm(false, _selectedStartMetode);
}

function pilihBelumBayar() {
    document.getElementById('modalStartPayCheck').style.display = 'none';
    _belumBayarTvIds[_pendingStartTvId] = true;
    _submitStartForm(true, 'Pending');
}

function _submitStartForm(isBelumBayar, metode) {
    const formId = _pendingStartType === 'fixed'
        ? `form-fixed-${_pendingStartTvId}`
        : `form-open-${_pendingStartTvId}`;
    const form = document.getElementById(formId);
    if (!form) return;

    ['payment_method','payment_status','bypass_pay_check'].forEach(n => {
        const old = form.querySelector(`[name="${n}"]`);
        if (old) old.remove();
    });

    const addHidden = (name, value) => {
        const inp = document.createElement('input');
        inp.type='hidden'; inp.name=name; inp.value=value;
        form.appendChild(inp);
    };

    addHidden('payment_method', metode);
    addHidden('payment_status', isBelumBayar ? 'pending' : 'paid');
    addHidden('bypass_pay_check', '1');

    const memberSelect = form.querySelector('select[name="member_id"]');
    const memberId     = memberSelect ? memberSelect.value : '';

    if (memberId && !form.querySelector('[name="bypass_saldo"]')) {
        pendingStartForm = form;
        const fd = new FormData(form);
        fetch('process.php?ajax=1', { method: 'POST', body: fd })
            .then(r => r.json())
            .then(data => {
                if (data.status === 'saldo_kurang') { showSaldoKurangModal(data); }
                else { form.submit(); }
            })
            .catch(() => form.submit());
    } else {
        form.submit();
    }
}

// ===================================================================
// BELUM BAYAR EXPIRED
// ===================================================================
let _bbExpTvId   = null;
let _bbExpTrxId  = null;
let _bbExpMetode = METODE_LIST[0].nama;

function _showBelumBayarExpiredModal(tvId, trxId) {
    _bbExpTvId   = tvId;
    _bbExpTrxId  = trxId || null;
    _bbExpMetode = METODE_LIST[0].nama;

    const card   = document.getElementById('card-' + tvId);
    const tvName = card?.querySelector('.tv-title')?.textContent || 'TV ' + tvId;
    document.getElementById('bbexp_tvname').textContent = tvName + ' — sesi telah berakhir';

    renderMetodeGrid('bbexp_metode_grid', (m) => { _bbExpMetode = m; });

    const btn = document.getElementById('btn_bayar_setelah_sesi');
    btn.disabled = false;
    btn.innerHTML = '<i class="bx bx-check-circle"></i> KONFIRMASI PEMBAYARAN & SELESAI';

    document.getElementById('modalBelumBayarExpired').style.display = 'flex';

    if (!document.getElementById(`timeout-notif-${tvId}`)) {
        const notif = document.createElement('div');
        notif.id = `timeout-notif-${tvId}`;
        notif.style.cssText = `position:fixed;top:20px;right:20px;z-index:99998;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;padding:20px 25px;border-radius:16px;box-shadow:0 10px 40px rgba(239,68,68,0.5);animation:slideIn .3s ease;font-weight:700;backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);min-width:320px;`;
        notif.innerHTML = `<div style="display:flex;align-items:center;gap:12px;"><i class='bx bxs-error-alt bx-tada' style="font-size:32px;"></i><div><div style="font-size:1.1rem;">⚠️ TV ${tvId}: BELUM BAYAR!</div><div style="font-size:.8rem;opacity:.9;">Waktu habis — minta pelanggan bayar sekarang.</div></div></div>`;
        document.body.appendChild(notif);
    }
}

function konfirmasiBayarSetelahSesi() {
    if (!_bbExpTvId) return;
    const btn = document.getElementById('btn_bayar_setelah_sesi');
    btn.disabled = true;
    btn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Memproses...';

    const fd = new FormData();
    fd.append('action',         'update_payment_method');
    fd.append('tv_id',          _bbExpTvId);
    fd.append('payment_method', _bbExpMetode);
    fd.append('payment_status', 'paid');
    fd.append('ajax',           '1');
    if (_bbExpTrxId) fd.append('trx_id', _bbExpTrxId);

    fetch('process.php', { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            delete _belumBayarTvIds[_bbExpTvId];
            _removeExpiredFromStorage(_bbExpTvId);
            document.getElementById('modalBelumBayarExpired').style.display = 'none';
            document.getElementById(`timeout-notif-${_bbExpTvId}`)?.remove();
            if (data.status === 'success') {
                showToast(`✓ Pembayaran ${_bbExpMetode} dikonfirmasi!`, 'success');
            } else {
                showToast(`⚠ ${data.msg || 'Pembayaran dikonfirmasi (mungkin ada masalah DB)'}`, 'warning');
            }
            setTimeout(() => window.location.href = 'index.php', 1200);
        })
        .catch(() => {
            delete _belumBayarTvIds[_bbExpTvId];
            _removeExpiredFromStorage(_bbExpTvId);
            document.getElementById('modalBelumBayarExpired').style.display = 'none';
            showToast('✓ Pembayaran dikonfirmasi', 'success');
            setTimeout(() => window.location.href = 'index.php', 1200);
        });
}

// ===================================================================
// VOUCHER MENIT (di form start)
// ===================================================================
function cekVoucherMenit(tvId, formType, btn) {
    const inp       = document.getElementById(`voucher_menit_kode_${formType}_${tvId}`);
    const resultDiv = document.getElementById(`vmenit_result_${formType}_${tvId}`);
    const hiddenId  = document.getElementById(`voucher_menit_id_${formType}_${tvId}`);
    const kode      = inp.value.trim();

    if (!kode) { showToast('Masukkan kode voucher menit dulu', 'warning'); return; }

    btn.disabled = true;
    btn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i>';

    const fd = new FormData();
    fd.append('action', 'validate_voucher_menit');
    fd.append('kode',   kode);
    fd.append('tv_id',  tvId);

    fetch('process.php', { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            btn.disabled = false;
            btn.innerHTML = 'CEK';

            if (data.status !== 'success') {
                resultDiv.style.display = 'block';
                resultDiv.style.background = 'rgba(239,68,68,.1)';
                resultDiv.style.border = '1px solid rgba(239,68,68,.25)';
                resultDiv.style.color = '#f87171';
                resultDiv.innerHTML = `<i class='bx bx-x-circle'></i> ${escapeHtml(data.msg)}`;
                hiddenId.value = '';
                return;
            }

            hiddenId.value = data.voucher_id;
            resultDiv.style.display = 'block';
            resultDiv.style.background = 'rgba(16,185,129,.1)';
            resultDiv.style.border = '1px solid rgba(16,185,129,.3)';
            resultDiv.style.color = '#34d399';
            resultDiv.innerHTML = `<i class='bx bx-check-circle'></i> <b>${escapeHtml(data.kode)}</b> — Tambah <b style="color:#fff">+${data.tambah_menit} menit</b> gratis!`;
            showToast(`✓ Voucher menit valid! +${data.tambah_menit} menit`, 'success');
        })
        .catch(() => {
            btn.disabled = false;
            btn.innerHTML = 'CEK';
            showToast('✗ Gagal cek voucher menit', 'error');
        });
}
</script>