1531 lines
72 KiB
HTML
1531 lines
72 KiB
HTML
<!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> |