Files
Kasir-Shop/index.html
T
2026-10-28 21:37:23 -04:00

1531 lines
72 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Warung Modern Suroboyo - Red POS & E-Commerce</title>
<style>
:root {
--primary: #d32f2f;
--primary-dark: #9a0007;
--accent: #b71c1c;
--bg: #fdf7f7;
--border: #f0cfcf;
--success: #2e7d32;
--warning: #ed6c02;
--danger: #c62828;
--purple: #880e4f;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
body { background-color: var(--bg); color: #212529; line-height: 1.5; padding-bottom: 70px; }
.hidden { display: none !important; }
.container { width: 100%; max-width: 960px; margin: 0 auto; padding: 16px; }
header { background: #fff; border-bottom: 2px solid var(--primary); position: sticky; top: 0; z-index: 100; box-shadow: 0 2px 8px rgba(211,47,47,0.08); }
.nav-inner { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; max-width: 960px; margin: 0 auto; }
.logo { font-size: 1.2rem; font-weight: 800; color: var(--primary); }
.badge-role { padding: 4px 10px; border-radius: 99px; font-size: 11px; font-weight: 700; text-transform: uppercase; color: #fff; }
.badge-admin { background-color: var(--purple); }
.badge-user { background-color: var(--primary); }
.badge-customer { background-color: #e65100; }
.auth-container { max-width: 420px; margin: 40px auto; padding: 26px; background: #fff; border-radius: 16px; box-shadow: 0 8px 24px rgba(211,47,47,0.08); border-top: 5px solid var(--primary); }
.auth-tabs { display: flex; gap: 8px; margin-bottom: 20px; border-bottom: 2px solid var(--border); }
.auth-tab { flex: 1; padding: 10px; background: none; border: none; font-weight: 700; cursor: pointer; color: #6c757d; }
.auth-tab.active { color: var(--primary); border-bottom: 3px solid var(--primary); margin-bottom: -2px; }
.form-group { margin-bottom: 14px; }
.form-group label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 5px; }
.form-control { width: 100%; padding: 9px 12px; border: 1.5px solid var(--border); border-radius: 8px; font-size: 14px; }
.form-control:focus { outline: none; border-color: var(--primary); }
.btn { display: inline-flex; align-items: center; justify-content: center; padding: 8px 14px; border-radius: 8px; font-size: 13px; font-weight: 600; border: none; cursor: pointer; gap: 6px; transition: transform 0.15s ease, background 0.2s; text-decoration: none; }
.btn:active { transform: scale(0.96); }
.btn-block { width: 100%; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-success { background: var(--success); color: #fff; }
.btn-danger { background: var(--danger); color: #fff; }
.btn-warning { background: var(--warning); color: #fff; }
.btn-secondary { background: #f0e4e4; color: #555; }
.btn-dark { background: #212529; color: #fff; }
.btn-sm { padding: 5px 9px; font-size: 11px; }
.link-supplier-box { margin-top: 20px; padding-top: 16px; border-top: 1px dashed var(--border); text-align: center; }
.cust-nav-tabs { display: flex; gap: 8px; margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
.cust-tab-btn { flex: 1; padding: 10px; background: #fff; border: 1.5px solid var(--border); border-radius: 10px; font-size: 13px; font-weight: 700; cursor: pointer; color: #555; }
.cust-tab-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.card-panel { background: #fff; padding: 18px; border-radius: 12px; margin-bottom: 20px; border: 1px solid var(--border); }
.table-container { background: #fff; border-radius: 8px; overflow-x: auto; border: 1px solid var(--border); margin-top: 10px; }
table { width: 100%; border-collapse: collapse; text-align: left; font-size: 13px; min-width: 550px; }
th, td { padding: 10px 12px; border-bottom: 1px solid var(--border); }
th { background: #fff5f5; font-weight: 700; }
.pill { display: inline-block; padding: 3px 8px; border-radius: 6px; font-size: 11px; font-weight: 700; }
.pill-pending { background: #fff3cd; color: #856404; }
.pill-lunas { background: #cce5ff; color: #004085; }
.pill-done { background: #d1e7dd; color: #0f5132; }
.pill-rejected { background: #f8d7da; color: #721c24; }
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; margin-bottom: 20px; }
@media (min-width: 650px) { .product-grid { grid-template-columns: repeat(3, 1fr); gap: 16px; } }
.product-card { background: #fff; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; padding: 12px; transition: transform 0.2s ease, box-shadow 0.2s; }
.product-img { width: 100%; height: 80px; background: #ffebee; display: flex; align-items: center; justify-content: center; font-size: 34px; border-radius: 8px; margin-bottom: 8px; }
.product-name { font-size: 13px; font-weight: 700; }
.product-price { font-size: 14px; font-weight: 800; color: var(--primary); margin: 4px 0; }
.stock-label { font-size: 11px; font-weight: bold; margin-bottom: 8px; }
@keyframes kranjangBounce {
0% { transform: scale(1); }
40% { transform: scale(1.08) translateY(-4px); }
70% { transform: scale(0.97); }
100% { transform: scale(1); }
}
.animasi-tambah {
animation: kranjangBounce 0.45s ease-out;
border-color: var(--primary) !important;
box-shadow: 0 0 14px rgba(211, 47, 47, 0.35) !important;
}
#cart-toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: #212529;
color: #fff;
padding: 10px 18px;
border-radius: 50px;
font-size: 13px;
font-weight: 600;
box-shadow: 0 6px 18px rgba(0,0,0,0.25);
z-index: 2000;
opacity: 0;
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
pointer-events: none;
}
#cart-toast.muncul {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
.payment-container { background: #fff; border-radius: 16px; padding: 22px; text-align: center; border: 2px dashed var(--primary); margin-top: 20px; max-width: 500px; margin-left: auto; margin-right: auto; box-shadow: 0 8px 24px rgba(0,0,0,0.06); }
.box-metode { background: #fff5f5; border: 1px solid var(--border); border-radius: 12px; padding: 16px; margin: 14px 0; text-align: center; }
.qris-card {
background: #ffffff;
border: 2px solid #212529;
border-radius: 12px;
padding: 14px;
display: inline-block;
margin: 10px auto;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.qris-header {
font-weight: 900;
font-size: 16px;
color: #d32f2f;
letter-spacing: 2px;
border-bottom: 2px solid #212529;
padding-bottom: 4px;
margin-bottom: 10px;
}
.svg-qris-dummy {
display: block;
margin: 0 auto;
width: 190px;
height: 190px;
}
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 1000; display: flex; justify-content: center; align-items: center; }
.modal-content { background: #fff; padding: 22px; border-radius: 16px; width: 95%; max-width: 880px; max-height: 90vh; overflow-y: auto; position: relative; }
.btn-close { position: absolute; top: 14px; right: 14px; border: none; background: #fee; color: red; font-weight: bold; padding: 4px 10px; border-radius: 6px; cursor: pointer; }
/* Gaya Navigasi Paginasi Geser */
.pagination-container {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.pagination-bar {
display: flex;
gap: 4px;
align-items: center;
overflow-x: auto;
padding: 4px 0;
max-width: 100%;
}
.page-btn {
padding: 5px 10px;
font-size: 12px;
font-weight: 600;
border: 1px solid var(--border);
background: #fff;
color: #333;
border-radius: 6px;
cursor: pointer;
min-width: 32px;
text-align: center;
}
.page-btn:hover:not(:disabled) {
background: #fdf2f2;
border-color: var(--primary);
color: var(--primary);
}
.page-btn.active {
background: var(--primary);
border-color: var(--primary);
color: #fff;
}
.page-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
/* Tab Filter Antrean */
.filter-tabs {
display: flex;
gap: 8px;
margin-top: 12px;
border-bottom: 1px solid var(--border);
padding-bottom: 8px;
flex-wrap: wrap;
}
.filter-tab-btn {
padding: 6px 14px;
border-radius: 20px;
border: 1.5px solid var(--border);
background: #fff;
font-size: 12px;
font-weight: bold;
color: #555;
cursor: pointer;
transition: all 0.2s;
}
.filter-tab-btn.active {
background: var(--primary);
color: #fff;
border-color: var(--primary);
}
</style>
</head>
<body>
<div id="cart-toast">Barang berhasil dimasukkan ke keranjang!</div>
<!-- ================= TOP HEADER ================= -->
<header id="main-header" class="hidden">
<div class="nav-inner">
<div class="logo">Warung Amba POS</div>
<div style="display: flex; align-items: center; gap: 8px;">
<button id="btn-db-juragan" class="btn btn-dark btn-sm hidden" onclick="bukaModalDb()">🗄️Cadangkan Database</button>
<span id="nav-info"></span>
<button class="btn btn-secondary btn-sm" onclick="logout()">Keluar</button>
</div>
</div>
</header>
<div class="container">
<!-- ================= HALAMAN LOGIN / DAFTAR ================= -->
<div id="page-auth" class="auth-container">
<div class="auth-tabs">
<button class="auth-tab active" id="tab-login" onclick="gantiAuthTab('login')">Masuk (Login)</button>
<button class="auth-tab" id="tab-reg" onclick="gantiAuthTab('reg')">Daftar Akun</button>
</div>
<form id="form-login" onsubmit="handleLogin(event)">
<div class="form-group">
<label>Nama Pengguna (Username)</label>
<input type="text" id="login-user" class="form-control" placeholder="Masukkan username" required>
</div>
<div class="form-group">
<label>Kata Sandi (Password)</label>
<input type="password" id="login-pass" class="form-control" placeholder="Masukkan password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Masuk Sekarang</button>
<div style="text-align: center; margin-top: 14px;">
<a href="javascript:void(0)" onclick="bukaModalResetPass()" style="font-size: 12px; color: var(--primary); text-decoration: none; font-weight: 600;">Lupa Kata Sandi (Reset Password)?</a>
</div>
<div id="auth-msg" style="margin-top: 10px; font-size: 13px; text-align: center; font-weight: bold; color: var(--danger);"></div>
</form>
<form id="form-reg" class="hidden" onsubmit="handleRegister(event)">
<div class="form-group">
<label>Nama Lengkap</label>
<input type="text" id="reg-name" class="form-control" placeholder="Nama lengkap Anda" required>
</div>
<div class="form-group">
<label>Nama Pengguna (Username)</label>
<input type="text" id="reg-user" class="form-control" placeholder="Username akun" required>
</div>
<div class="form-group">
<label>Kata Sandi</label>
<input type="password" id="reg-pass" class="form-control" placeholder="Kata sandi" required>
</div>
<div class="form-group">
<label>Daftar Sebagai</label>
<select id="reg-role" class="form-control">
<option value="customer">Pelanggan (Customer - Langsung Aktif)</option>
<option value="user">Karyawan Toko (Kasir - Butuh Persetujuan Juragan)</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-block">Daftar Sekarang</button>
</form>
<div class="link-supplier-box">
<p style="font-size: 12px; color: #777; margin-bottom: 8px;">Apakah Anda Mitra Pemasok atau Ekspedisi?</p>
<a href="supplier.html" class="btn btn-dark btn-sm btn-block">🚚 Masuk Portal Pemasok / Sales ↗</a>
</div>
</div>
<!-- ================= DASHBOARD CUSTOMER ================= -->
<div id="view-customer" class="hidden">
<div class="cust-nav-tabs">
<button class="cust-tab-btn active" id="btn-tab-toko" onclick="gantiTabCust('toko')">🛍️ Belanja & Menu</button>
<button class="cust-tab-btn" id="btn-tab-profil" onclick="gantiTabCust('profil')">👤 Profil Akun</button>
</div>
<div id="sec-cust-toko">
<div id="payment-box" class="hidden payment-container">
<h3 style="color: var(--primary); margin-bottom: 4px;">Pilihan Pembayaran</h3>
<p style="font-size: 14px; margin: 4px 0;">Total Tagihan: <strong>Rp <span id="pay-total">0</span></strong></p>
<p style="font-size: 12px; color: #666;">No. Pesanan: <strong id="pay-id"></strong></p>
<div class="form-group" style="text-align: left; margin-top: 14px;">
<label>Pilih Metode Pembayaran:</label>
<select id="metode-bayar" class="form-control" onchange="gantiInstruksiBayar()">
<option value="qris">QRIS All Payment (Langsung Lunas Otomatis)</option>
<option value="transfer">Transfer Bank BCA/Mandiri (Langsung Lunas Otomatis)</option>
<option value="tunai">Bayar Tunai di Kasir (Perlu Konfirmasi Kasir/Juragan)</option>
</select>
</div>
<div id="box-qris" class="box-metode">
<p style="font-size: 13px; font-weight: bold; margin-bottom: 6px;">Pindai Barcode QRIS Ini (Otomatis Lunas):</p>
<div class="qris-card">
<div class="qris-header">QRIS 🇮🇩</div>
<svg class="svg-qris-dummy" viewBox="0 0 220 220">
<rect width="220" height="220" fill="#ffffff"/>
<rect x="15" y="15" width="55" height="55" fill="#000"/><rect x="23" y="23" width="39" height="39" fill="#fff"/><rect x="31" y="31" width="23" height="23" fill="#d32f2f"/>
<rect x="150" y="15" width="55" height="55" fill="#000"/><rect x="158" y="23" width="39" height="39" fill="#fff"/><rect x="166" y="31" width="23" height="23" fill="#d32f2f"/>
<rect x="15" y="150" width="55" height="55" fill="#000"/><rect x="23" y="158" width="39" height="39" fill="#fff"/><rect x="31" y="166" width="23" height="23" fill="#d32f2f"/>
<rect x="80" y="20" width="12" height="12" fill="#000"/><rect x="100" y="20" width="12" height="12" fill="#000"/><rect x="120" y="20" width="12" height="12" fill="#000"/>
<rect x="80" y="40" width="12" height="12" fill="#000"/><rect x="120" y="40" width="12" height="12" fill="#000"/><rect x="90" y="58" width="12" height="12" fill="#000"/>
<rect x="110" y="58" width="12" height="12" fill="#000"/><rect x="20" y="80" width="12" height="12" fill="#000"/><rect x="40" y="80" width="12" height="12" fill="#000"/>
<rect x="58" y="100" width="12" height="12" fill="#000"/><rect x="20" y="120" width="12" height="12" fill="#000"/><rect x="58" y="120" width="12" height="12" fill="#000"/>
<rect x="150" y="80" width="12" height="12" fill="#000"/><rect x="170" y="80" width="12" height="12" fill="#000"/><rect x="190" y="80" width="12" height="12" fill="#000"/>
<rect x="150" y="100" width="12" height="12" fill="#000"/><rect x="190" y="100" width="12" height="12" fill="#000"/><rect x="170" y="120" width="12" height="12" fill="#000"/>
<rect x="80" y="150" width="12" height="12" fill="#000"/><rect x="100" y="150" width="12" height="12" fill="#000"/><rect x="120" y="150" width="12" height="12" fill="#000"/>
<rect x="80" y="170" width="12" height="12" fill="#000"/><rect x="110" y="170" width="12" height="12" fill="#000"/><rect x="90" y="190" width="12" height="12" fill="#000"/>
<rect x="150" y="150" width="55" height="12" fill="#000"/><rect x="150" y="170" width="20" height="35" fill="#000"/><rect x="185" y="170" width="20" height="35" fill="#000"/>
<rect x="80" y="80" width="60" height="60" rx="8" fill="#d32f2f"/><rect x="88" y="88" width="44" height="44" rx="6" fill="#fff"/>
<text x="110" y="115" font-size="13" font-family="sans-serif" font-weight="900" fill="#d32f2f" text-anchor="middle">QRIS</text>
</svg>
<div style="font-size: 11px; font-weight: bold; margin-top: 4px; color: #333;">NMID: ID1020304050607</div>
</div>
<p style="font-size: 11px; color: #666; margin-top: 4px;">*Otomatis disetujui sistem tanpa menunggu kasir.</p>
</div>
<div id="box-transfer" class="box-metode hidden" style="text-align: left;">
<p style="font-size: 13px; font-weight: bold; margin-bottom: 6px;">Rekening Resmi Warung (Langsung Lunas):</p>
<p style="font-size: 13px; margin-bottom: 4px;">• <strong>BCA:</strong> <code>876543210</code> (a/n Warung Amba)</p>
<p style="font-size: 13px;">• <strong>Mandiri:</strong> <code>142000987654</code> (a/n Warung Amba)</p>
<small style="color: red;">*Otomatis disetujui sistem tanpa menunggu kasir.</small>
</div>
<div id="box-tunai" class="box-metode hidden">
<p style="font-size: 13px; font-weight: bold;">Bayar Tunai di Kasir</p>
<p style="font-size: 12px; color: #555; margin-top: 4px;">Pesanan akan <strong>menunggu persetujuan kasir/juragan</strong> setelah uang fisik diterima.</p>
</div>
<div style="display: flex; gap: 8px; justify-content: center; margin-top: 14px;">
<button class="btn btn-secondary btn-sm" onclick="batalBayar()">Nanti Dulu</button>
<button class="btn btn-success btn-sm" onclick="konfirmasiBayar()">Konfirmasi Pembayaran</button>
</div>
</div>
<div id="cust-main">
<h3 style="margin-bottom: 12px;">Etalase Menu Warung</h3>
<div class="product-grid" id="customer-products"></div>
<!-- Keranjang Belanja -->
<div class="card-panel" id="panel-kranjang-box">
<h3 id="kranjang-header">🛒 Keranjang Belanja Anda</h3>
<div id="cart-list" style="margin: 10px 0;">Keranjang masih kosong.</div>
<!-- Input Voucher Diskon -->
<div style="background: #fff5f5; border: 1px dashed var(--border); border-radius: 8px; padding: 10px; margin: 12px 0;">
<div style="display: flex; gap: 6px;">
<input type="text" id="input-voucher" class="form-control" placeholder="Masukkan Kode Voucher (contoh: DISKON12)" style="text-transform: uppercase;">
<button class="btn btn-dark btn-sm" onclick="terapkanVoucher()">Gunakan</button>
</div>
<div id="voucher-msg" style="font-size: 11px; margin-top: 4px; font-weight: bold;"></div>
</div>
<div style="display: flex; justify-content: space-between; font-size: 13px; color: #555;">
<span>Subtotal:</span>
<span>Rp <span id="cart-subtotal">0</span></span>
</div>
<div id="row-diskon" class="hidden" style="display: flex; justify-content: space-between; font-size: 13px; color: var(--danger); font-weight: bold;">
<span>Potongan Diskon (<span id="diskon-persen-label">0</span>%):</span>
<span>- Rp <span id="cart-potongan">0</span></span>
</div>
<div style="display: flex; justify-content: space-between; font-weight: bold; font-size: 16px; margin-top: 6px; border-top: 1px solid #eee; padding-top: 6px;">
<span>Total Akhir:</span>
<span style="color: var(--primary);">Rp <span id="cart-total">0</span></span>
</div>
<button class="btn btn-primary btn-block" style="margin-top: 12px;" onclick="checkoutCustomer()">Checkout Pesanan Sekarang</button>
</div>
<div class="card-panel">
<h3>Status Pesanan Anda</h3>
<div class="table-container">
<table>
<thead><tr><th>No. Pesanan</th><th>Rincian</th><th>Total</th><th>Status</th><th>Aksi</th></tr></thead>
<tbody id="cust-orders-table"></tbody>
</table>
</div>
</div>
</div>
</div>
<div id="sec-cust-profil" class="hidden">
<div class="card-panel" style="max-width: 450px; margin: 0 auto;">
<h3>👤 Profil Akun</h3>
<form onsubmit="handleUpdateProfil(event)" style="margin-top: 10px;">
<div class="form-group"><label>Username</label><input type="text" id="prof-user" class="form-control" disabled style="background:#eee;"></div>
<div class="form-group"><label>Nama Lengkap</label><input type="text" id="prof-name" class="form-control" required></div>
<div class="form-group"><label>Password Baru</label><input type="password" id="prof-pass" class="form-control" placeholder="Kosongkan jika tidak diubah"></div>
<button type="submit" class="btn btn-primary btn-block">Simpan Perubahan</button>
</form>
</div>
</div>
</div>
<!-- ================= DASHBOARD STAFF / JURAGAN ================= -->
<div id="view-staff" class="hidden">
<div id="panel-juragan-extra" class="hidden" style="margin-bottom: 16px; display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap;">
<a href="supplier.html" target="_blank" class="btn btn-dark btn-sm">🚚 Buka Portal Pemasok & Kiriman</a>
<a href="rekening.html" target="_blank" class="btn btn-primary btn-sm">💳 Saldo & Rekening Toko (M-Banking)</a>
<button class="btn btn-secondary btn-sm" onclick="bukaModalAkun()">🛡️ Database Pengguna</button>
</div>
<!-- KARTU RINGKASAN ANTREAN JURAGAN -->
<div class="card-panel" style="background: linear-gradient(to right, #fff, #fff7f7); border-left: 5px solid var(--primary); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;">
<div>
<h3 style="color: var(--primary);">📋 Daftar Antrean Pesanan Toko</h3>
<p style="font-size: 13px; color: #666; margin-top: 2px;">
Kelola antrean dan verifikasi pembayaran kasir secara rapi melalui jendela pop-up.
</p>
<div style="margin-top: 6px; font-size: 12px;">
Total Terdata: <strong id="summary-total-order" style="color: var(--primary);">0</strong> Pesanan
(<span id="summary-pending-order" style="color: var(--warning); font-weight: bold;">0 Menunggu</span>)
</div>
</div>
<div>
<button class="btn btn-primary" onclick="bukaModalAntrean()" style="padding: 10px 18px; font-size: 14px; box-shadow: 0 4px 10px rgba(211,47,47,0.25);">
🔍 Buka Pop-up Antrean (<span id="btn-count-order">0</span>)
</button>
</div>
</div>
<!-- Setting Voucher Diskon -->
<div class="card-panel" style="border-left: 5px solid var(--warning);">
<h3 style="color: var(--warning); margin-bottom: 8px;">🏷️ Pengaturan Kode Voucher Toko</h3>
<form onsubmit="handleSimpanVoucher(event)" style="display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap;">
<div style="flex: 1; min-width: 140px;">
<label style="font-size: 12px; font-weight: bold;">Kode Voucher</label>
<input type="text" id="set-voc-kode" class="form-control" style="text-transform: uppercase;" required>
</div>
<div style="width: 130px;">
<label style="font-size: 12px; font-weight: bold;">Diskon (%)</label>
<input type="number" id="set-voc-diskon" class="form-control" min="1" max="100" required>
</div>
<div>
<button type="submit" class="btn btn-warning" style="height: 38px;">Simpan Voucher</button>
</div>
</form>
</div>
<!-- Verifikasi Karyawan Baru -->
<div id="panel-acc-karyawan" class="card-panel hidden" style="border-left: 5px solid var(--primary);">
<h3 style="color: var(--primary);">👥 Verifikasi Karyawan Baru</h3>
<div class="table-container">
<table>
<thead><tr><th>Nama</th><th>Username</th><th>Status</th><th>Aksi Juragan</th></tr></thead>
<tbody id="table-acc-karyawan"></tbody>
</table>
</div>
</div>
<!-- Monitoring Pengiriman Supplier -->
<div id="panel-supplier-connect" class="card-panel hidden" style="border-left: 5px solid #0288d1;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h3 style="color: #0288d1;">🚚 Notifikasi Pengiriman Pemasok (Koneksi Langsung)</h3>
<a href="supplier.html" target="_blank" style="font-size: 12px; color: #0288d1; font-weight: bold; text-decoration: none;">Buka Halaman Pemasok ↗</a>
</div>
<div class="table-container">
<table>
<thead><tr><th>No. Resi</th><th>Pemasok</th><th>Barang & Jumlah</th><th>Status Pengiriman</th><th>Aksi Terima Stok</th></tr></thead>
<tbody id="table-juragan-supplier"></tbody>
</table>
</div>
</div>
<!-- Tabel Stok & Input Barang Baru -->
<div class="card-panel">
<h3>📦 Monitoring Stok Barang & Tambah Menu Baru</h3>
<form onsubmit="handleTambahBarang(event)" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)) auto; gap: 8px; align-items: end; margin-top: 10px;">
<div><label style="font-size: 12px; font-weight: bold;">Nama Barang</label><input type="text" id="add-nama" class="form-control" required></div>
<div><label style="font-size: 12px; font-weight: bold;">Harga (Rp)</label><input type="number" id="add-harga" class="form-control" required></div>
<div><label style="font-size: 12px; font-weight: bold;">Stok Awal</label><input type="number" id="add-stok" class="form-control" required></div>
<div><label style="font-size: 12px; font-weight: bold;">Stok Minimal</label><input type="number" id="add-min" class="form-control" required></div>
<button type="submit" class="btn btn-success" style="height: 38px;">+ Tambah</button>
</form>
<div class="table-container">
<table>
<thead><tr><th>No</th><th>Barang</th><th>Harga</th><th>Sisa Stok</th><th>Batas Min</th><th>Status</th><th>Aksi Tindakan</th></tr></thead>
<tbody id="master-stock-table"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- ================= POP-UP MODAL ANTREAN ================= -->
<div id="modal-antrean" class="modal-overlay hidden">
<div class="modal-content">
<button class="btn-close" onclick="tutupModalAntrean()">X</button>
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; padding-right: 32px;">
<div>
<h3 style="color: var(--primary);">📋 Daftar Antrean Pesanan Toko</h3>
<p style="font-size: 12px; color: #666;">Verifikasi pesanan dan kelola status transaksi pelanggan asli.</p>
</div>
<!-- TOMBOL PEMBERSIH ANTREAN -->
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
<button class="btn btn-warning btn-sm" onclick="bersihkanAntreanFilter()" title="Hapus pesanan pada kategori saat ini">🧹 Clear Kategori</button>
<button class="btn btn-danger btn-sm" onclick="bersihkanSemuaAntrean()" title="Kosongkan seluruh antrean">🗑️ Clear Semua</button>
</div>
</div>
<!-- KATEGORI FILTER ANTREAN -->
<div class="filter-tabs">
<button class="filter-tab-btn active" id="tab-filter-all" onclick="gantiFilterKategori('all')">
Semua (<span id="count-kategori-all">0</span>)
</button>
<button class="filter-tab-btn" id="tab-filter-pending" onclick="gantiFilterKategori('pending')">
⏳ Menunggu Acc (<span id="count-kategori-pending">0</span>)
</button>
<button class="filter-tab-btn" id="tab-filter-acc" onclick="gantiFilterKategori('acc')">
✓ Sudah di-Acc / Lunas (<span id="count-kategori-acc">0</span>)
</button>
</div>
<div class="table-container" style="margin-top: 10px;">
<table>
<thead>
<tr>
<th>ID Pesanan</th>
<th>Pelanggan</th>
<th>Daftar Menu</th>
<th>Total Bayar</th>
<th>Status Pembayaran</th>
<th>Aksi Juragan/Kasir</th>
</tr>
</thead>
<tbody id="staff-orders-table"></tbody>
</table>
</div>
<!-- KONTROL PAGINASI GESER -->
<div class="pagination-container">
<div style="font-size: 12px; color: #555;">
Halaman <strong id="current-page-num">1</strong> dari <strong id="total-page-num">1</strong>
(Total: <span id="total-records-num">0</span> data)
</div>
<div class="pagination-bar" id="pagination-controls">
<!-- Tombol halaman digenerate otomatis -->
</div>
</div>
</div>
</div>
<!-- ================= MODAL RESET PASSWORD ================= -->
<div id="modal-reset-pass" class="modal-overlay hidden">
<div class="modal-content" style="max-width: 420px;">
<button class="btn-close" onclick="tutupModalResetPass()">X</button>
<h3 style="margin-bottom: 12px; color: var(--primary);">🔑 Reset Kata Sandi Akun</h3>
<p style="font-size: 12px; color: #666; margin-bottom: 14px;">Masukkan username dan tentukan kata sandi baru Anda di bawah ini:</p>
<form onsubmit="handleProsesResetPassword(event)">
<div class="form-group">
<label>Username</label>
<input type="text" id="reset-user" class="form-control" placeholder="Nama login Anda" required>
</div>
<div class="form-group">
<label>Kata Sandi Baru</label>
<input type="password" id="reset-pass" class="form-control" placeholder="Masukkan sandi baru" required>
</div>
<div class="form-group">
<label>Ulangi Kata Sandi Baru</label>
<input type="password" id="reset-pass-confirm" class="form-control" placeholder="Ketik ulang sandi baru" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Simpan Sandi Baru</button>
</form>
</div>
</div>
<!-- ================= MODAL BACKUP DATABASE ================= -->
<div id="modal-db" class="modal-overlay hidden">
<div class="modal-content">
<button class="btn-close" onclick="tutupModalDb()">X</button>
<h3 style="margin-bottom: 10px; color: var(--primary);">🗄️ Cadangkan & Pulihkan Database (Khusus Juragan)</h3>
<p style="font-size: 12px; color: #666; margin-bottom: 12px;">Unduh salinan basis data dalam format JSON agar seluruh data transaksi, akun, dan stok tersimpan aman.</p>
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px;">
<button class="btn btn-success btn-sm" onclick="downloadBackup()">⬇️ Unduh Cadangan (.json)</button>
<button class="btn btn-primary btn-sm" onclick="document.getElementById('file-restore').click()">⬆️ Unggah / Pulihkan Database</button>
<input type="file" id="file-restore" accept=".json" class="hidden" onchange="uploadBackup(event)">
<button class="btn btn-danger btn-sm" onclick="resetDatabaseBawaan()">⚠️ Reset ke Setelan Bawaan</button>
</div>
<pre id="db-preview" style="background: #212529; color: #4ec9b0; padding: 12px; border-radius: 8px; font-size: 11px; max-height: 250px; overflow: auto;"></pre>
</div>
</div>
<!-- ================= MODAL USER JURAGAN ================= -->
<div id="modal-akun" class="modal-overlay hidden">
<div class="modal-content">
<button class="btn-close" onclick="tutupModalAkun()">X</button>
<h3 style="margin-bottom: 10px; color: var(--purple);">👥 Database Pengguna (Khusus Juragan)</h3>
<div class="table-container">
<table>
<thead><tr><th>No</th><th>Nama</th><th>Username</th><th>Sandi</th><th>Peran</th><th>Status</th><th>Hapus</th></tr></thead>
<tbody id="master-akun-table"></tbody>
</table>
</div>
</div>
</div>
<script>
// ================= 1. DATABASE & STORAGE =================
const defaultAccounts = [
{ name: "Juragan Besar", user: "juragan", pass: "admin123", role: "admin", status: "aktif" },
{ name: "Yefta", user: "Pemilik", pass: "admin", role: "admin", status: "aktif" },
{ name: "Karyawan Kasir", user: "karyawan", pass: "user123", role: "user", status: "aktif" },
{ name: "Budi", user: "pembeli", pass: "123", role: "customer", status: "aktif" }
];
const defaultProducts = [
{ id: 1, nama: "Kopi Hitam Gula Aren", harga: 12000, stok: 45, stokMin: 5, icon: "☕" },
{ id: 2, nama: "Nasi Bebek Sambal Korek", harga: 35000, stok: 25, stokMin: 3, icon: "🍗" },
{ id: 3, nama: "Es Teh Manis Jumbo", harga: 5000, stok: 60, stokMin: 10, icon: "🧋" },
{ id: 4, nama: "Ayam Goreng Lengkuas", harga: 28000, stok: 20, stokMin: 4, icon: "🍗" },
{ id: 5, nama: "Tahu & Tempe Mendoan", harga: 10000, stok: 35, stokMin: 5, icon: "🥪" }
];
function getStorage(k, fb) { const d = localStorage.getItem(k); return d ? JSON.parse(d) : fb; }
function setStorage(k, v) { localStorage.setItem(k, JSON.stringify(v)); }
let dbAkun = getStorage("db_akun", defaultAccounts);
let dbBarang = getStorage("db_barang", defaultProducts);
let dbPesanan = getStorage("db_pesanan", []); // Murni dimulai kosong / mengikuti pesanan nyata
let dbSupplier = getStorage("db_supplier_pengiriman", []);
let dbVoucher = getStorage("db_voucher", { kode: "HEMAT30", diskon: 30 });
let keranjang = [];
let voucherTerpasang = null;
let orderLagiDibayarId = null;
let sesiLogin = getStorage("aktif_user", null);
// Konfigurasi Paginasi & Kategori
let halSekarang = 1;
const perHalaman = 5;
let kategoriAktif = 'all';
window.onload = function() {
if (sesiLogin) {
document.getElementById("page-auth").classList.add("hidden");
document.getElementById("main-header").classList.remove("hidden");
bukaAplikasi();
} else {
document.getElementById("page-auth").classList.remove("hidden");
document.getElementById("main-header").classList.add("hidden");
}
};
// ================= 2. LOGIN & RESET PASSWORD =================
function gantiAuthTab(t) {
document.getElementById("tab-login").classList.toggle("active", t==='login');
document.getElementById("tab-reg").classList.toggle("active", t==='reg');
document.getElementById("form-login").classList.toggle("hidden", t!=='login');
document.getElementById("form-reg").classList.toggle("hidden", t!=='reg');
document.getElementById("auth-msg").innerText = "";
}
function handleRegister(e) {
e.preventDefault();
const name = document.getElementById("reg-name").value.trim();
const user = document.getElementById("reg-user").value.trim();
const pass = document.getElementById("reg-pass").value;
const role = document.getElementById("reg-role").value;
if (dbAkun.some(u => u.user.toLowerCase() === user.toLowerCase())) {
return document.getElementById("auth-msg").innerText = "Nama pengguna sudah dipakai!";
}
const status = (role === "customer") ? "aktif" : "pending";
dbAkun.push({ name, user, pass, role, status });
setStorage("db_akun", dbAkun);
alert(role === "user" ? "Pendaftaran Karyawan berhasil! Menunggu persetujuan Juragan." : "Akun Pelanggan berhasil dibuat! Silakan masuk.");
gantiAuthTab('login');
document.getElementById("form-reg").reset();
}
function handleLogin(e) {
e.preventDefault();
const user = document.getElementById("login-user").value.trim();
const pass = document.getElementById("login-pass").value;
const match = dbAkun.find(u => u.user === user && u.pass === pass);
if (!match) return document.getElementById("auth-msg").innerText = "Nama pengguna atau kata sandi keliru!";
if (match.status === "pending") return document.getElementById("auth-msg").innerText = "Akun Karyawan belum disetujui Juragan!";
if (match.status === "ditolak") return document.getElementById("auth-msg").innerText = "Pendaftaran Karyawan ditolak oleh Juragan!";
sesiLogin = match;
setStorage("aktif_user", sesiLogin);
document.getElementById("page-auth").classList.add("hidden");
document.getElementById("main-header").classList.remove("hidden");
bukaAplikasi();
}
function bukaModalResetPass() {
document.getElementById("modal-reset-pass").classList.remove("hidden");
}
function tutupModalResetPass() {
document.getElementById("modal-reset-pass").classList.add("hidden");
}
function handleProsesResetPassword(e) {
e.preventDefault();
const username = document.getElementById("reset-user").value.trim();
const passAnyar = document.getElementById("reset-pass").value;
const passConfirm = document.getElementById("reset-pass-confirm").value;
if (passAnyar !== passConfirm) return alert("Kata sandi baru dan konfirmasi tidak sesuai!");
const akun = dbAkun.find(u => u.user.toLowerCase() === username.toLowerCase());
if (!akun) return alert("Nama pengguna tidak ditemukan di database!");
akun.pass = passAnyar;
setStorage("db_akun", dbAkun);
alert(`Kata sandi akun @${username} berhasil diperbarui! Silakan masuk.`);
tutupModalResetPass();
}
function logout() {
sesiLogin = null;
localStorage.removeItem("aktif_user");
keranjang = [];
voucherTerpasang = null;
document.getElementById("view-customer").classList.add("hidden");
document.getElementById("view-staff").classList.add("hidden");
document.getElementById("main-header").classList.add("hidden");
document.getElementById("page-auth").classList.remove("hidden");
}
function bukaAplikasi() {
const role = sesiLogin.role;
const isJuragan = (role === "admin");
let badge = isJuragan ? "Juragan" : (role === "user" ? "Karyawan Kasir" : "Pelanggan");
document.getElementById("nav-info").innerHTML = `<strong>${sesiLogin.name}</strong> <span class="badge-role badge-${role}">${badge}</span>`;
document.getElementById("btn-db-juragan").classList.toggle("hidden", !isJuragan);
if (role === "customer") {
document.getElementById("view-customer").classList.remove("hidden");
gantiTabCust('toko');
renderKatalogCust();
renderKeranjang();
renderPesananCust();
} else {
document.getElementById("view-staff").classList.remove("hidden");
document.getElementById("panel-juragan-extra").classList.toggle("hidden", !isJuragan);
document.getElementById("panel-acc-karyawan").classList.toggle("hidden", !isJuragan);
document.getElementById("panel-supplier-connect").classList.toggle("hidden", !isJuragan);
document.getElementById("set-voc-kode").value = dbVoucher.kode;
document.getElementById("set-voc-diskon").value = dbVoucher.diskon;
updateRingkasanAntrean();
renderMasterStok();
if (isJuragan) {
renderAccKaryawan();
renderSupplierConnectJuragan();
}
}
}
// ================= 3. SETTING VOUCHER DISKON =================
function handleSimpanVoucher(e) {
e.preventDefault();
const kode = document.getElementById("set-voc-kode").value.trim().toUpperCase();
const diskon = parseInt(document.getElementById("set-voc-diskon").value, 10);
dbVoucher = { kode, diskon };
setStorage("db_voucher", dbVoucher);
alert(`Voucher berhasil diperbarui! Kode: "${kode}" dengan diskon ${diskon}%.`);
}
function terapkanVoucher() {
const inputKode = document.getElementById("input-voucher").value.trim().toUpperCase();
const msg = document.getElementById("voucher-msg");
if (inputKode === dbVoucher.kode) {
voucherTerpasang = dbVoucher;
msg.style.color = "var(--success)";
msg.innerText = `✓ Kode berhasil dipasang! Diskon ${dbVoucher.diskon}% telah aktif.`;
} else {
voucherTerpasang = null;
msg.style.color = "var(--danger)";
msg.innerText = `✕ Kode voucher tidak valid atau sudah kedaluwarsa!`;
}
renderKeranjang();
}
// ================= 4. CUSTOMER =================
function gantiTabCust(t) {
document.getElementById("btn-tab-toko").classList.toggle("active", t==='toko');
document.getElementById("btn-tab-profil").classList.toggle("active", t==='profil');
document.getElementById("sec-cust-toko").classList.toggle("hidden", t!=='toko');
document.getElementById("sec-cust-profil").classList.toggle("hidden", t!=='profil');
if (t === 'profil') {
document.getElementById("prof-user").value = sesiLogin.user;
document.getElementById("prof-name").value = sesiLogin.name;
}
}
function handleUpdateProfil(e) {
e.preventDefault();
const target = dbAkun.find(u => u.user === sesiLogin.user);
target.name = document.getElementById("prof-name").value.trim();
if (document.getElementById("prof-pass").value) target.pass = document.getElementById("prof-pass").value;
sesiLogin = target;
setStorage("db_akun", dbAkun);
setStorage("aktif_user", sesiLogin);
alert("Profil berhasil diperbarui!");
bukaAplikasi();
}
function renderKatalogCust() {
const box = document.getElementById("customer-products");
box.innerHTML = "";
dbBarang.forEach(item => {
const habis = item.stok <= 0;
const mepet = item.stok > 0 && item.stok <= item.stokMin;
let stokHtml = habis
? `<span style="color:red;">❌ Stok Habis</span>`
: (mepet ? `<span style="color:#ed6c02;">⚠️ Sisa ${item.stok}</span>` : `Sisa: ${item.stok}`);
box.innerHTML += `
<div class="product-card" id="card-item-${item.id}">
<div class="product-img">${item.icon || '📦'}</div>
<div class="product-name">${item.nama}</div>
<div class="product-price">Rp ${item.harga.toLocaleString("id-ID")}</div>
<div class="stock-label">${stokHtml}</div>
<button class="btn btn-primary btn-sm btn-block" onclick="tambahKeranjang(${item.id})" ${habis ? 'disabled style="opacity:0.5;"' : ''}>+ Keranjang</button>
</div>
`;
});
}
function tambahKeranjang(id) {
const b = dbBarang.find(x => x.id === id);
const ada = keranjang.find(k => k.id === id);
const totalBeli = (ada ? ada.qty : 0) + 1;
if (totalBeli > b.stok) return alert(`Stok yang tersisa hanya ${b.stok}!`);
if (ada) ada.qty++; else keranjang.push({ ...b, qty: 1 });
renderKeranjang();
const cardElem = document.getElementById(`card-item-${id}`);
if (cardElem) {
cardElem.classList.remove("animasi-tambah");
void cardElem.offsetWidth;
cardElem.classList.add("animasi-tambah");
}
const toast = document.getElementById("cart-toast");
toast.innerText = `🛒 +1 ${b.nama} masuk ke keranjang!`;
toast.classList.add("muncul");
setTimeout(() => { toast.classList.remove("muncul"); }, 2000);
}
function ubahQty(id, delta) {
const b = dbBarang.find(x => x.id === id);
const item = keranjang.find(k => k.id === id);
if (!item) return;
if (delta > 0 && item.qty + 1 > b.stok) return alert("Stok sudah mencapai batas maksimal!");
item.qty += delta;
if (item.qty <= 0) keranjang = keranjang.filter(k => k.id !== id);
renderKeranjang();
}
function renderKeranjang() {
const box = document.getElementById("cart-list");
if (keranjang.length === 0) {
box.innerHTML = "<em>Keranjang masih kosong.</em>";
document.getElementById("cart-subtotal").innerText = "0";
document.getElementById("cart-total").innerText = "0";
document.getElementById("row-diskon").classList.add("hidden");
return;
}
let subtotal = 0, html = "";
keranjang.forEach(item => {
subtotal += item.harga * item.qty;
html += `<div style="display:flex; justify-content:space-between; margin-bottom:6px;">
<div><strong>${item.nama}</strong><br><small>Rp ${item.harga.toLocaleString("id-ID")} x ${item.qty}</small></div>
<div><button class="btn btn-secondary btn-sm" onclick="ubahQty(${item.id}, -1)">-</button> <button class="btn btn-secondary btn-sm" onclick="ubahQty(${item.id}, 1)">+</button></div>
</div>`;
});
box.innerHTML = html;
document.getElementById("cart-subtotal").innerText = subtotal.toLocaleString("id-ID");
let potongan = 0;
if (voucherTerpasang) {
potongan = Math.round((subtotal * voucherTerpasang.diskon) / 100);
document.getElementById("row-diskon").classList.remove("hidden");
document.getElementById("diskon-persen-label").innerText = voucherTerpasang.diskon;
document.getElementById("cart-potongan").innerText = potongan.toLocaleString("id-ID");
} else {
document.getElementById("row-diskon").classList.add("hidden");
}
const totalAkhir = Math.max(0, subtotal - potongan);
document.getElementById("cart-total").innerText = totalAkhir.toLocaleString("id-ID");
}
function checkoutCustomer() {
if (keranjang.length === 0) return alert("Keranjang belanja masih kosong!");
keranjang.forEach(item => {
const target = dbBarang.find(b => b.id === item.id);
if (target) target.stok -= item.qty;
});
setStorage("db_barang", dbBarang);
const subtotal = keranjang.reduce((acc, c) => acc + (c.harga * c.qty), 0);
let potongan = voucherTerpasang ? Math.round((subtotal * voucherTerpasang.diskon) / 100) : 0;
let totalAkhir = Math.max(0, subtotal - potongan);
const orderId = "ORD-" + Math.floor(100000 + Math.random() * 900000);
const orderAnyar = {
orderId: orderId,
customerUser: sesiLogin.user,
customerName: sesiLogin.name,
items: [...keranjang],
subtotal: subtotal,
diskon: potongan,
total: totalAkhir,
voucherDigunakake: voucherTerpasang ? voucherTerpasang.kode : null,
status: "Pending Pembayaran",
metodeBayar: "",
disetujuiOleh: null
};
dbPesanan.unshift(orderAnyar);
setStorage("db_pesanan", dbPesanan);
keranjang = [];
voucherTerpasang = null;
document.getElementById("input-voucher").value = "";
document.getElementById("voucher-msg").innerText = "";
renderKeranjang();
renderKatalogCust();
renderPesananCust();
bukaPapanBayar(orderId);
}
function gantiInstruksiBayar() {
const metode = document.getElementById("metode-bayar").value;
document.getElementById("box-qris").classList.toggle("hidden", metode !== "qris");
document.getElementById("box-transfer").classList.toggle("hidden", metode !== "transfer");
document.getElementById("box-tunai").classList.toggle("hidden", metode !== "tunai");
}
function bukaPapanBayar(orderId) {
const o = dbPesanan.find(x => x.orderId === orderId);
if (!o) return;
orderLagiDibayarId = orderId;
document.getElementById("pay-id").innerText = "#" + o.orderId;
document.getElementById("pay-total").innerText = o.total.toLocaleString("id-ID");
document.getElementById("metode-bayar").value = "qris";
gantiInstruksiBayar();
document.getElementById("cust-main").classList.add("hidden");
document.getElementById("payment-box").classList.remove("hidden");
}
function batalBayar() {
orderLagiDibayarId = null;
document.getElementById("payment-box").classList.add("hidden");
document.getElementById("cust-main").classList.remove("hidden");
renderPesananCust();
}
function konfirmasiBayar() {
const o = dbPesanan.find(x => x.orderId === orderLagiDibayarId);
if (!o) return;
const metode = document.getElementById("metode-bayar").value;
const selectElem = document.getElementById("metode-bayar");
const metodeTeks = selectElem.options[selectElem.selectedIndex].text;
o.metodeBayar = metodeTeks;
if (metode === "qris" || metode === "transfer") {
o.status = "Sudah Lunas (Auto-Acc)";
o.disetujuiOleh = "Sistem Otomatis";
alert(`Terima kasih! Pembayaran melalui ${metodeTeks} telah berhasil diverifikasi dan LUNAS.`);
} else {
o.status = "Menunggu Kasir/Juragan (Bayar Tunai)";
o.disetujuiOleh = null;
alert(`Pesanan berhasil dicatat! Silakan menuju kasir untuk melakukan pembayaran tunai.`);
}
setStorage("db_pesanan", dbPesanan);
batalBayar();
}
function renderPesananCust() {
const tbody = document.getElementById("cust-orders-table");
tbody.innerHTML = "";
const pesananKula = dbPesanan.filter(o => o.customerUser === sesiLogin.user);
if (pesananKula.length === 0) return tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;">Belum pernah memesan.</td></tr>`;
pesananKula.forEach(o => {
let pill = o.status.includes("Lunas") ? "pill-done" : (o.status.includes("Menunggu") || o.status.includes("Pending") ? "pill-pending" : (o.status.includes("Ditolak") ? "pill-rejected" : "pill-pending"));
let btnBayar = o.status === "Pending Pembayaran" ? `<button class="btn btn-warning btn-sm" onclick="bukaPapanBayar('${o.orderId}')">Bayar</button>` : "-";
tbody.innerHTML += `<tr>
<td>#${o.orderId}</td>
<td>${o.items.map(i => i.nama + ' (' + i.qty + ')').join(", ")}</td>
<td>Rp ${o.total.toLocaleString("id-ID")}</td>
<td><span class="pill ${pill}">${o.status}</span></td>
<td>${btnBayar}</td>
</tr>`;
});
}
// ================= 5. STAFF & JURAGAN (ANTREAN ASLI, KATEGORI & CLEAR) =================
function updateRingkasanAntrean() {
const total = dbPesanan.length;
const pending = dbPesanan.filter(o => o.status.includes("Menunggu") || o.status.includes("Pending")).length;
const acc = dbPesanan.filter(o => o.status.includes("Lunas") || o.status.includes("Disetujui")).length;
document.getElementById("summary-total-order").innerText = total;
document.getElementById("summary-pending-order").innerText = `${pending} Menunggu`;
document.getElementById("btn-count-order").innerText = total;
const elAll = document.getElementById("count-kategori-all");
const elPend = document.getElementById("count-kategori-pending");
const elAcc = document.getElementById("count-kategori-acc");
if (elAll) elAll.innerText = total;
if (elPend) elPend.innerText = pending;
if (elAcc) elAcc.innerText = acc;
}
function bukaModalAntrean() {
document.getElementById("modal-antrean").classList.remove("hidden");
halSekarang = 1;
renderAntreanStaff();
}
function tutupModalAntrean() {
document.getElementById("modal-antrean").classList.add("hidden");
updateRingkasanAntrean();
}
function gantiFilterKategori(kat) {
kategoriAktif = kat;
halSekarang = 1;
document.getElementById("tab-filter-all").classList.toggle("active", kat === 'all');
document.getElementById("tab-filter-pending").classList.toggle("active", kat === 'pending');
document.getElementById("tab-filter-acc").classList.toggle("active", kat === 'acc');
renderAntreanStaff();
}
function filterDaftarPesanan() {
if (kategoriAktif === 'pending') {
return dbPesanan.filter(o => o.status.includes("Menunggu") || o.status.includes("Pending"));
} else if (kategoriAktif === 'acc') {
return dbPesanan.filter(o => o.status.includes("Lunas") || o.status.includes("Disetujui"));
}
return dbPesanan;
}
function renderAntreanStaff() {
const tbody = document.getElementById("staff-orders-table");
tbody.innerHTML = "";
const listTerfilter = filterDaftarPesanan();
const totalRecords = listTerfilter.length;
const totalPages = Math.max(1, Math.ceil(totalRecords / perHalaman));
if (halSekarang > totalPages) halSekarang = totalPages;
if (halSekarang < 1) halSekarang = 1;
document.getElementById("current-page-num").innerText = halSekarang;
document.getElementById("total-page-num").innerText = totalPages;
document.getElementById("total-records-num").innerText = totalRecords;
if (totalRecords === 0) {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; padding: 20px;">Belum ada data pesanan dalam kategori ini.</td></tr>`;
document.getElementById("pagination-controls").innerHTML = "";
updateRingkasanAntrean();
return;
}
const start = (halSekarang - 1) * perHalaman;
const end = start + perHalaman;
const dataHalaman = listTerfilter.slice(start, end);
dataHalaman.forEach(o => {
let pill = o.status.includes("Lunas") ? "pill-done" : (o.status.includes("Menunggu") || o.status.includes("Pending") ? "pill-pending" : (o.status.includes("Ditolak") ? "pill-rejected" : "pill-pending"));
let aksi = "";
if (o.status.includes("Lunas")) {
aksi = `<span class="pill pill-done">✓ ${o.disetujuiOleh || 'Lunas'}</span>`;
} else if (o.status.includes("Ditolak")) {
aksi = `<span class="pill pill-rejected">${o.disetujuiOleh || 'Ditolak'}</span>`;
} else {
let btnAcc = `<button class="btn btn-success btn-sm" onclick="setujuiPesanan('${o.orderId}')">Acc & Lunas</button>`;
let btnTolak = `<button class="btn btn-danger btn-sm" onclick="tolakPesanan('${o.orderId}')">Tolak</button>`;
aksi = `<div style="display:flex; gap:4px;">${btnAcc} ${btnTolak}</div>`;
}
let discInfo = o.diskon > 0 ? `<br><small style="color:var(--danger);">Diskon: -Rp ${o.diskon.toLocaleString("id-ID")}</small>` : "";
tbody.innerHTML += `<tr>
<td><strong>#${o.orderId}</strong></td>
<td>${o.customerName}</td>
<td>${o.items.map(i => i.nama + ' (' + i.qty + ')').join(", ")}</td>
<td><strong>Rp ${o.total.toLocaleString("id-ID")}</strong>${discInfo}</td>
<td><span class="pill ${pill}">${o.status}</span><br><small style="color:#0288d1;">${o.metodeBayar || 'Belum memilih pembayaran'}</small></td>
<td>${aksi}</td>
</tr>`;
});
renderKontrolPaginasi(totalPages);
updateRingkasanAntrean();
}
function renderKontrolPaginasi(totalPages) {
const container = document.getElementById("pagination-controls");
container.innerHTML = "";
const btnAwal = document.createElement("button");
btnAwal.className = "page-btn";
btnAwal.innerHTML = "« Awal";
btnAwal.disabled = halSekarang === 1;
btnAwal.onclick = () => { halSekarang = 1; renderAntreanStaff(); };
container.appendChild(btnAwal);
const btnPrev = document.createElement("button");
btnPrev.className = "page-btn";
btnPrev.innerHTML = "‹ Prev";
btnPrev.disabled = halSekarang === 1;
btnPrev.onclick = () => { halSekarang--; renderAntreanStaff(); };
container.appendChild(btnPrev);
let rentangAwal = Math.max(1, halSekarang - 3);
let rentangAkhir = Math.min(totalPages, halSekarang + 3);
if (rentangAwal > 1) {
const btnTitikAwal = document.createElement("span");
btnTitikAwal.innerText = "...";
btnTitikAwal.style.padding = "0 4px";
container.appendChild(btnTitikAwal);
}
for (let p = rentangAwal; p <= rentangAkhir; p++) {
const btnPage = document.createElement("button");
btnPage.className = `page-btn ${p === halSekarang ? 'active' : ''}`;
btnPage.innerText = p;
btnPage.onclick = () => { halSekarang = p; renderAntreanStaff(); };
container.appendChild(btnPage);
}
if (rentangAkhir < totalPages) {
const btnTitikAkhir = document.createElement("span");
btnTitikAkhir.innerText = "...";
btnTitikAkhir.style.padding = "0 4px";
container.appendChild(btnTitikAkhir);
}
const btnNext = document.createElement("button");
btnNext.className = "page-btn";
btnNext.innerHTML = "Next ›";
btnNext.disabled = halSekarang === totalPages;
btnNext.onclick = () => { halSekarang++; renderAntreanStaff(); };
container.appendChild(btnNext);
const btnAkhir = document.createElement("button");
btnAkhir.className = "page-btn";
btnAkhir.innerHTML = "Akhir »";
btnAkhir.disabled = halSekarang === totalPages;
btnAkhir.onclick = () => { halSekarang = totalPages; renderAntreanStaff(); };
container.appendChild(btnAkhir);
}
// ================= FITUR BERSIHKAN ANTREAN =================
function bersihkanAntreanFilter() {
let labelKategori = "semua pesanan";
if (kategoriAktif === 'pending') labelKategori = "pesanan yang meminta Acc (menunggu kasir)";
if (kategoriAktif === 'acc') labelKategori = "pesanan yang sudah di-Acc / lunas";
if (!confirm(`Apakah Anda yakin ingin membersihkan ${labelKategori}?`)) return;
if (kategoriAktif === 'pending') {
dbPesanan = dbPesanan.filter(o => !(o.status.includes("Menunggu") || o.status.includes("Pending")));
} else if (kategoriAktif === 'acc') {
dbPesanan = dbPesanan.filter(o => !(o.status.includes("Lunas") || o.status.includes("Disetujui")));
} else {
dbPesanan = [];
}
setStorage("db_pesanan", dbPesanan);
halSekarang = 1;
renderAntreanStaff();
alert(`Antrean pada kategori terpilih berhasil dibersihkan!`);
}
function bersihkanSemuaAntrean() {
if (confirm("Peringatan: Seluruh riwayat dan antrean pesanan akan dikosongkan. Lanjutkan?")) {
dbPesanan = [];
setStorage("db_pesanan", dbPesanan);
halSekarang = 1;
renderAntreanStaff();
alert("Semua data antrean berhasil dikosongkan!");
}
}
function setujuiPesanan(orderId) {
const o = dbPesanan.find(x => x.orderId === orderId);
if (!o) return;
o.status = "Sudah Lunas (Diverifikasi Kasir/Juragan)";
o.disetujuiOleh = sesiLogin.role === "admin" ? "Juragan" : "Kasir";
setStorage("db_pesanan", dbPesanan);
renderAntreanStaff();
alert(`Pesanan tunai #${orderId} berhasil disetujui & ditandai Lunas!`);
}
function tolakPesanan(orderId) {
const o = dbPesanan.find(x => x.orderId === orderId);
if (!o) return;
const alesan = prompt("Alasan penolakan pesanan:", "Pembatalan transaksi / uang tunai tidak mencukupi");
if (!alesan) return;
o.status = "Ditolak (" + alesan + ")";
o.disetujuiOleh = sesiLogin.role === "admin" ? "Juragan" : "Kasir";
o.items.forEach(item => {
const target = dbBarang.find(b => b.id === item.id);
if (target) target.stok += item.qty;
});
setStorage("db_pesanan", dbPesanan);
setStorage("db_barang", dbBarang);
renderAntreanStaff();
renderMasterStok();
alert(`Pesanan #${orderId} ditolak dan stok barang telah dikembalikan.`);
}
// TABEL STOK (Karyawan & Juragan dapat mengedit dan menambah stok)
function renderMasterStok() {
const tbody = document.getElementById("master-stock-table");
tbody.innerHTML = "";
const isJuragan = sesiLogin.role === "admin";
dbBarang.forEach((item, idx) => {
let status = item.stok <= 0 ? `<span class="pill pill-rejected">Habis</span>` : (item.stok <= item.stokMin ? `<span class="pill pill-pending">Menipis</span>` : `<span class="pill pill-done">Aman</span>`);
let btnTambahStok = `<button class="btn btn-success btn-sm" onclick="tambahStokCepet(${item.id})" title="Tambah stok cepat">+ Stok</button>`;
let btnEdit = `<button class="btn btn-warning btn-sm" onclick="editBarangSemua(${item.id})">Edit</button>`;
let btnHapus = isJuragan ? `<button class="btn btn-danger btn-sm" onclick="hapusBarang(${item.id})">Hapus</button>` : ``;
tbody.innerHTML += `<tr>
<td>${idx + 1}</td>
<td>${item.icon || '📦'} <strong>${item.nama}</strong></td>
<td>Rp ${item.harga.toLocaleString("id-ID")}</td>
<td><strong style="font-size:14px;">${item.stok}</strong></td>
<td>${item.stokMin}</td>
<td>${status}</td>
<td>
<div style="display:flex; gap:4px; flex-wrap:wrap;">
${btnTambahStok}
${btnEdit}
${btnHapus}
</div>
</td>
</tr>`;
});
}
function tambahStokCepet(id) {
const item = dbBarang.find(b => b.id === id);
if (!item) return;
const tambah = prompt(`Tambah berapa unit stok untuk "${item.nama}"? (Stok saat ini: ${item.stok})`, "10");
if (tambah === null) return;
const jml = parseInt(tambah, 10);
if (!isNaN(jml) && jml > 0) {
item.stok += jml;
setStorage("db_barang", dbBarang);
renderMasterStok();
alert(`Stok "${item.nama}" berhasil ditambah ${jml} unit! (Total saat ini: ${item.stok})`);
} else {
alert("Jumlah stok harus berupa angka lebih dari 0!");
}
}
function editBarangSemua(id) {
const item = dbBarang.find(b => b.id === id);
if (!item) return;
const anyarNama = prompt("Ubah Nama Barang / Menu:", item.nama);
if (anyarNama === null) return;
const anyarHarga = prompt("Ubah Harga Barang (Rp):", item.harga);
if (anyarHarga === null) return;
const anyarStok = prompt("Atur Total Sisa Stok Saat Ini:", item.stok);
if (anyarStok === null) return;
const anyarMin = prompt("Atur Batas Stok Minimal:", item.stokMin);
if (anyarMin === null) return;
if (anyarNama.trim() && !isNaN(anyarHarga) && !isNaN(anyarStok) && !isNaN(anyarMin)) {
item.nama = anyarNama.trim();
item.harga = parseInt(anyarHarga, 10);
item.stok = parseInt(anyarStok, 10);
item.stokMin = parseInt(anyarMin, 10);
setStorage("db_barang", dbBarang);
renderMasterStok();
alert(`Data barang "${item.nama}" berhasil diperbarui!`);
} else {
alert("Input tidak valid! Harga dan stok harus berupa angka.");
}
}
function handleTambahBarang(e) {
e.preventDefault();
dbBarang.push({
id: Date.now(),
nama: document.getElementById("add-nama").value.trim(),
harga: parseInt(document.getElementById("add-harga").value, 10),
stok: parseInt(document.getElementById("add-stok").value, 10),
stokMin: parseInt(document.getElementById("add-min").value, 10),
icon: "📦"
});
setStorage("db_barang", dbBarang);
document.getElementById("add-nama").value = "";
document.getElementById("add-harga").value = "";
document.getElementById("add-stok").value = "";
document.getElementById("add-min").value = "";
renderMasterStok();
alert("Menu/Barang baru berhasil ditambahkan ke etalase!");
}
function hapusBarang(id) {
if (confirm("Apakah Anda yakin ingin menghapus barang ini dari katalog?")) {
dbBarang = dbBarang.filter(b => b.id !== id);
setStorage("db_barang", dbBarang);
renderMasterStok();
}
}
// ================= 6. SUPPLIER & VERIFIKASI =================
function renderSupplierConnectJuragan() {
const tbody = document.getElementById("table-juragan-supplier");
tbody.innerHTML = "";
dbSupplier = getStorage("db_supplier_pengiriman", []);
if (dbSupplier.length === 0) return tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;">Belum ada kiriman dari pemasok.</td></tr>`;
dbSupplier.forEach(s => {
let aksi = s.status === "Dikirim"
? `<button class="btn btn-success btn-sm" onclick="terimaKirimanSupplier(${s.id})">Terima & Tambah Stok</button>`
: `<span style="color:green; font-weight:bold;">✓ Diterima Juragan</span>`;
tbody.innerHTML += `<tr>
<td><code>#${s.resi}</code></td>
<td><strong>${s.namaSupplier}</strong><br><small>${s.salesHp}</small></td>
<td>${s.namaBarang} (<strong>+${s.jumlahStok}</strong> unit)</td>
<td><span class="pill ${s.status==='Diterima' ? 'pill-done' : 'pill-pending'}">${s.status}</span></td>
<td>${aksi}</td>
</tr>`;
});
}
function terimaKirimanSupplier(id) {
const s = dbSupplier.find(x => x.id === id);
if (!s) return;
let b = dbBarang.find(x => x.nama.toLowerCase() === s.namaBarang.toLowerCase());
if (b) {
b.stok += parseInt(s.jumlahStok, 10);
} else {
dbBarang.push({
id: Date.now(),
nama: s.namaBarang,
harga: parseInt(s.hargaBeli, 10) * 1.2,
stok: parseInt(s.jumlahStok, 10),
stokMin: 5,
icon: "📦"
});
}
s.status = "Diterima";
setStorage("db_supplier_pengiriman", dbSupplier);
setStorage("db_barang", dbBarang);
renderSupplierConnectJuragan();
renderMasterStok();
alert(`Kiriman dari ${s.namaSupplier} berhasil diterima! Stok ${s.namaBarang} otomatis bertambah.`);
}
function renderAccKaryawan() {
const tbody = document.getElementById("table-acc-karyawan");
tbody.innerHTML = "";
const userList = dbAkun.filter(u => u.role === "user");
if (userList.length === 0) return tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;">Tidak ada pendaftaran karyawan baru.</td></tr>`;
userList.forEach(u => {
let aksi = u.status === "pending"
? `<button class="btn btn-success btn-sm" onclick="accKaryawan('${u.user}', 'aktif')">Setujui</button> <button class="btn btn-danger btn-sm" onclick="accKaryawan('${u.user}', 'ditolak')">Tolak</button>`
: `<span style="font-size:11px;">${u.status}</span>`;
tbody.innerHTML += `<tr><td>${u.name}</td><td><code>@${u.user}</code></td><td>${u.status}</td><td>${aksi}</td></tr>`;
});
}
function accKaryawan(u, st) {
const target = dbAkun.find(x => x.user === u);
if (target) { target.status = st; setStorage("db_akun", dbAkun); renderAccKaryawan(); }
}
// ================= 7. BACKUP DATABASE (JURAGAN) =================
function bukaModalDb() {
if (sesiLogin.role !== "admin") return alert("Akses ditolak!");
document.getElementById("modal-db").classList.remove("hidden");
const dump = {
db_akun: dbAkun,
db_barang: dbBarang,
db_pesanan: dbPesanan,
db_supplier_pengiriman: getStorage("db_supplier_pengiriman", []),
db_akun_supplier: getStorage("db_akun_supplier", []),
db_voucher: dbVoucher
};
document.getElementById("db-preview").textContent = JSON.stringify(dump, null, 2);
}
function tutupModalDb() { document.getElementById("modal-db").classList.add("hidden"); }
function downloadBackup() {
if (sesiLogin.role !== "admin") return;
const dump = {
tanggalBackup: new Date().toISOString(),
db_akun: dbAkun,
db_barang: dbBarang,
db_pesanan: dbPesanan,
db_supplier_pengiriman: getStorage("db_supplier_pengiriman", []),
db_akun_supplier: getStorage("db_akun_supplier", []),
db_voucher: dbVoucher
};
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(dump, null, 2));
const a = document.createElement("a");
a.setAttribute("href", dataStr);
a.setAttribute("download", `backup_warung_${new Date().toISOString().slice(0,10)}.json`);
document.body.appendChild(a);
a.click();
a.remove();
}
function uploadBackup(e) {
if (sesiLogin.role !== "admin") return;
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(evt) {
try {
const d = JSON.parse(evt.target.result);
if (confirm("Seluruh basis data akan ditimpa dengan cadangan ini. Lanjutkan?")) {
setStorage("db_akun", d.db_akun || defaultAccounts);
setStorage("db_barang", d.db_barang || defaultProducts);
setStorage("db_pesanan", d.db_pesanan || []);
setStorage("db_supplier_pengiriman", d.db_supplier_pengiriman || []);
setStorage("db_akun_supplier", d.db_akun_supplier || []);
if (d.db_voucher) setStorage("db_voucher", d.db_voucher);
alert("Database berhasil dipulihkan!");
location.reload();
}
} catch(err) { alert("Format file JSON tidak valid!"); }
};
reader.readAsText(file);
}
function resetDatabaseBawaan() {
if (sesiLogin.role !== "admin") return;
if (confirm("Apakah Anda yakin ingin mengatur ulang semua data ke setelan awal?")) {
localStorage.clear();
location.reload();
}
}
function bukaModalAkun() {
document.getElementById("modal-akun").classList.remove("hidden");
const tbody = document.getElementById("master-akun-table");
tbody.innerHTML = "";
dbAkun.forEach((a, i) => {
tbody.innerHTML += `<tr>
<td>${i+1}</td><td>${a.name}</td><td><code>${a.user}</code></td>
<td style="color:red; font-weight:bold;">${a.pass}</td><td>${a.role}</td><td>${a.status}</td>
<td>${a.role!=='admin' ? `<button class="btn btn-danger btn-sm" onclick="hapusUser('${a.user}')">Hapus</button>` : '-'}</td>
</tr>`;
});
}
function tutupModalAkun() { document.getElementById("modal-akun").classList.add("hidden"); }
function hapusUser(u) {
if (confirm(`Hapus akun pengguna ${u}?`)) {
dbAkun = dbAkun.filter(x => x.user !== u);
setStorage("db_akun", dbAkun);
bukaModalAkun();
}
}
</script>
</body>
</html>