Upload files to "/"

This commit is contained in:
2026-10-28 21:37:23 -04:00
commit 00af64f859
3 changed files with 2543 additions and 0 deletions
+536
View File
@@ -0,0 +1,536 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>M-Banking Toko - Rekening & Saldo Kas</title>
<style>
:root {
--primary: #d32f2f;
--primary-dark: #9a0007;
--accent: #b71c1c;
--bg: #f8f9fa;
--card-bg: #ffffff;
--text: #212529;
--border: #e9ecef;
--success: #2e7d32;
--danger: #c62828;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
body { background-color: var(--bg); color: var(--text); padding-bottom: 50px; }
.container { max-width: 520px; margin: 0 auto; padding: 16px; }
.top-bar { display: flex; justify-content: space-between; align-items: center; padding: 14px 0; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
.btn-back { text-decoration: none; color: #555; font-size: 13px; font-weight: bold; padding: 6px 12px; background: #eee; border-radius: 8px; }
.atm-card {
background: linear-gradient(135deg, #b71c1c, #d32f2f, #ef5350);
color: #fff;
border-radius: 18px;
padding: 22px;
box-shadow: 0 10px 24px rgba(211, 47, 47, 0.3);
position: relative;
overflow: hidden;
margin-bottom: 20px;
}
.atm-card::after {
content: "";
position: absolute;
width: 140px;
height: 140px;
background: rgba(255,255,255,0.08);
border-radius: 50%;
top: -40px;
right: -40px;
}
.chip { width: 38px; height: 28px; background: #ffd700; border-radius: 6px; margin-bottom: 16px; }
.saldo-title { font-size: 12px; opacity: 0.9; text-transform: uppercase; letter-spacing: 1px; }
.saldo-amount { font-size: 26px; font-weight: 900; margin: 4px 0 16px 0; }
.card-footer { display: flex; justify-content: space-between; align-items: flex-end; font-size: 12px; opacity: 0.9; }
.action-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px; }
.action-btn { background: #fff; border: 1px solid var(--border); border-radius: 12px; padding: 14px 8px; text-align: center; cursor: pointer; transition: all 0.2s; }
.action-btn:active { transform: scale(0.96); }
.action-icon { font-size: 22px; margin-bottom: 4px; display: block; }
.action-label { font-size: 11px; font-weight: bold; color: #444; }
.panel-box { background: #fff; border-radius: 14px; padding: 18px; border: 1px solid var(--border); margin-bottom: 18px; box-shadow: 0 2px 8px rgba(0,0,0,0.03); }
.rek-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px dashed #eee; font-size: 13px; }
.rek-row:last-child { border-bottom: none; }
.btn-copy { background: #fee; border: none; color: var(--primary); padding: 4px 8px; border-radius: 6px; font-size: 11px; font-weight: bold; cursor: pointer; }
.mutasi-item { display: flex; justify-content: space-between; align-items: flex-start; padding: 12px 0; border-bottom: 1px solid #f1f1f1; font-size: 13px; gap: 8px; }
.mutasi-item:last-child { border-bottom: none; }
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; z-index: 1000; }
.modal-content { background: #fff; width: 92%; max-width: 440px; border-radius: 16px; padding: 20px; position: relative; }
.form-group { margin-bottom: 12px; }
.form-group label { display: block; font-size: 12px; font-weight: bold; margin-bottom: 4px; }
.form-control { width: 100%; padding: 10px; border: 1.5px solid #ccc; border-radius: 8px; font-size: 14px; }
.btn { padding: 10px; border-radius: 8px; border: none; font-weight: bold; cursor: pointer; width: 100%; }
.btn-primary { background: var(--primary); color: #fff; }
.hidden { display: none !important; }
.badge-tag { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 10px; font-weight: bold; margin-bottom: 3px; }
.badge-in { background: #e8f5e9; color: var(--success); }
.badge-out { background: #ffebee; color: var(--danger); }
/* Filter & Search Mutasi */
.filter-group { display: flex; gap: 6px; margin-bottom: 12px; overflow-x: auto; padding-bottom: 4px; }
.filter-btn { padding: 6px 12px; border: 1px solid var(--border); border-radius: 20px; background: #fff; font-size: 11px; font-weight: bold; cursor: pointer; color: #555; white-space: nowrap; }
.filter-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
</style>
</head>
<body>
<div class="container">
<div class="top-bar">
<div>
<h3 style="color: var(--primary); font-weight: 800;">🏦 M-Banking Warung</h3>
<small style="color: #666;">Akses Khusus Pemilik Toko</small>
</div>
<a href="index.html" class="btn-back">← Kembali ke Toko</a>
</div>
<div class="atm-card">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div class="chip"></div>
<span style="font-weight: 800; font-size: 14px;">WARUNG PAY</span>
</div>
<div class="saldo-title">Total Saldo Kas Toko</div>
<div class="saldo-amount" id="mbank-saldo">Rp 0</div>
<div class="card-footer">
<div>
<span style="display: block; font-size: 10px;">PEMILIK REKENING</span>
<strong id="mbank-owner">PEMILIK TOKO</strong>
</div>
<div>
<span style="display: block; font-size: 10px;">NO. KARTU BISNIS</span>
<strong>5241 •••• •••• 8899</strong>
</div>
</div>
</div>
<div class="action-grid">
<div class="action-btn" onclick="bukaModalTransfer()">
<span class="action-icon">💸</span>
<span class="action-label">Tarik Saldo</span>
</div>
<div class="action-btn" onclick="bukaModalTopup()">
<span class="action-icon">📥</span>
<span class="action-label">Tambah Kas</span>
</div>
<div class="action-btn" onclick="salinSemuaRekening()">
<span class="action-icon">📋</span>
<span class="action-label">Salin Rekening</span>
</div>
</div>
<div class="panel-box">
<h4 style="margin-bottom: 12px; color: #333;">💳 Rekening Penampungan Toko</h4>
<div class="rek-row">
<div>
<strong>Bank Central Asia (BCA)</strong><br>
<code style="font-size: 14px; font-weight: bold; color: var(--primary);">876543210</code><br>
<small style="color: #666;">a/n Warung Amba</small>
</div>
<button class="btn-copy" onclick="salinTeks('876543210')">Salin</button>
</div>
<div class="rek-row">
<div>
<strong>Bank Mandiri</strong><br>
<code style="font-size: 14px; font-weight: bold; color: var(--primary);">142000987654</code><br>
<small style="color: #666;">a/n Warung Amba</small>
</div>
<button class="btn-copy" onclick="salinTeks('142000987654')">Salin</button>
</div>
<div class="rek-row">
<div>
<strong>NMID QRIS Nasional</strong><br>
<code style="font-size: 14px; font-weight: bold; color: var(--primary);">ID1020304050607</code><br>
<small style="color: #666;">Mendukung seluruh dompet digital & m-banking</small>
</div>
<button class="btn-copy" onclick="salinTeks('ID1020304050607')">Salin</button>
</div>
</div>
<!-- MUTASI LENGKAP KELUAR & MASUK BARANG -->
<div class="panel-box">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h4 style="color: #333;">📄 Mutasi Kas & Barang</h4>
<button onclick="bersihkanMutasiKas()" style="background: none; border: none; color: var(--danger); font-size: 11px; font-weight: bold; cursor: pointer;">
🗑️ Clear Kas
</button>
</div>
<!-- Filter dan Kolom Pencarian Mutasi -->
<div class="filter-group">
<button class="filter-btn active" id="filter-all" onclick="gantiFilterMutasi('all')">Semua</button>
<button class="filter-btn" id="filter-in" onclick="gantiFilterMutasi('in')">Masuk (Penjualan)</button>
<button class="filter-btn" id="filter-out" onclick="gantiFilterMutasi('out')">Keluar (Pasokan/Tarik)</button>
</div>
<input type="text" id="cari-mutasi" class="form-control" placeholder="🔍 Cari transaksi / menu..." style="margin-bottom: 12px; font-size: 12px; padding: 7px 10px;" oninput="muatDataRekening()">
<div id="mutasi-list"></div>
</div>
</div>
<!-- MODAL TARIK SALDO DENGAN PILIHAN TARIK SEMUA & REKENING TUJUAN -->
<div id="modal-transfer" class="modal-overlay hidden">
<div class="modal-content">
<h3 style="color: var(--primary); margin-bottom: 10px;">💸 Tarik Saldo Kas Toko</h3>
<form onsubmit="handleTarikSaldo(event)">
<div class="form-group">
<label>Bank / Tujuan Pencairan</label>
<select id="tarik-bank" class="form-control" onchange="gantiKategoriTujuan()">
<option value="BCA">BCA (Bank Central Asia)</option>
<option value="Mandiri">Bank Mandiri</option>
<option value="BRI">Bank BRI</option>
<option value="BNI">Bank BNI</option>
<option value="DANA/OVO/GoPay">E-Wallet (DANA / OVO / GoPay)</option>
<option value="Tunai Fisik">Tarik Tunai Kasir</option>
</select>
</div>
<div class="form-group" id="box-norek-tujuan">
<label>Nomor Rekening / Akun Tujuan</label>
<input type="text" id="tarik-norek" class="form-control" placeholder="Contoh: 5310982341 (a/n Pemilik)" required>
</div>
<div class="form-group">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<label style="margin: 0;">Nominal Penarikan (Rp)</label>
<button type="button" onclick="isiSemuaSaldo()" style="border: none; background: #ffebee; color: var(--primary); font-size: 11px; font-weight: bold; padding: 2px 8px; border-radius: 4px; cursor: pointer;">
⚡ Tarik Semua
</button>
</div>
<input type="number" id="tarik-nominal" class="form-control" placeholder="0" min="1000" required>
</div>
<div style="display: flex; gap: 8px; margin-top: 16px;">
<button type="button" class="btn" style="background:#eee; color:#333;" onclick="tutupModal()">Batal</button>
<button type="submit" class="btn btn-primary">Tarik Sekarang</button>
</div>
</form>
</div>
</div>
<div id="modal-topup" class="modal-overlay hidden">
<div class="modal-content">
<h3 style="color: var(--success); margin-bottom: 10px;">📥 Tambah Kas Manual</h3>
<form onsubmit="handleTopupKas(event)">
<div class="form-group">
<label>Keterangan Sumber Dana</label>
<input type="text" id="topup-ket" class="form-control" placeholder="Contoh: Setoran Modal Kasir" required>
</div>
<div class="form-group">
<label>Nominal (Rp)</label>
<input type="number" id="topup-nominal" class="form-control" placeholder="Contoh: 500000" min="1000" required>
</div>
<div style="display: flex; gap: 8px; margin-top: 16px;">
<button type="button" class="btn" style="background:#eee; color:#333;" onclick="tutupModal()">Batal</button>
<button type="submit" class="btn" style="background:var(--success); color:#fff;">Tambah Kas</button>
</div>
</form>
</div>
</div>
<script>
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 sesiToko = getStorage("aktif_user", null);
let filterAktif = 'all';
window.onload = function() {
if (!sesiToko || sesiToko.role !== "admin") {
alert("Akses ditolak. Halaman M-Banking ini khusus untuk Pemilik Toko.");
window.location.href = "index.html";
return;
}
document.getElementById("mbank-owner").innerText = sesiToko.name.toUpperCase();
muatDataRekening();
};
function hitungTotalSaldo() {
const dbPesanan = getStorage("db_pesanan", []);
const dbMutasiTambahan = getStorage("db_mutasi_mbal", []);
const dbSupplierPengiriman = getStorage("db_supplier_pengiriman", []);
// 1. Pemasukan dari pesanan yang berstatus lunas
let totalMasukPesanan = 0;
if (Array.isArray(dbPesanan)) {
dbPesanan.forEach(o => {
const st = (o.status || "").toLowerCase();
if (st.includes("lunas") || st.includes("selesai") || st.includes("verifikasi") || st.includes("auto-acc")) {
totalMasukPesanan += Number(o.total) || 0;
}
});
}
// 2. Pengeluaran otomatis untuk barang masuk dari pemasok (yang diterima)
let totalKeluarSupplier = 0;
if (Array.isArray(dbSupplierPengiriman)) {
dbSupplierPengiriman.forEach(s => {
if ((s.status || "").toLowerCase() === "diterima") {
const qty = Number(s.jumlahStok) || 0;
const harga = Number(s.hargaBeli) || 0;
totalKeluarSupplier += (qty * harga);
}
});
}
// 3. Mutasi kas manual (setoran & penarikan)
let penyesuaianKas = 0;
if (Array.isArray(dbMutasiTambahan)) {
dbMutasiTambahan.forEach(m => {
const nom = Number(m.nominal) || 0;
if (m.tipe === "masuk") penyesuaianKas += nom;
else penyesuaianKas -= nom;
});
}
return totalMasukPesanan - totalKeluarSupplier + penyesuaianKas;
}
function gantiFilterMutasi(tipe) {
filterAktif = tipe;
document.getElementById("filter-all").classList.toggle("active", tipe === 'all');
document.getElementById("filter-in").classList.toggle("active", tipe === 'in');
document.getElementById("filter-out").classList.toggle("active", tipe === 'out');
muatDataRekening();
}
function muatDataRekening() {
const totalSaldo = hitungTotalSaldo();
document.getElementById("mbank-saldo").innerText = "Rp " + totalSaldo.toLocaleString("id-ID");
const mutasiContainer = document.getElementById("mutasi-list");
if (!mutasiContainer) return;
mutasiContainer.innerHTML = "";
const dbPesanan = getStorage("db_pesanan", []);
const dbMutasiTambahan = getStorage("db_mutasi_mbal", []);
const dbSupplierPengiriman = getStorage("db_supplier_pengiriman", []);
let daftarMutasi = [];
// Mutasi Masuk: Penjualan Menu / Barang Keluar ke Pelanggan
if (Array.isArray(dbPesanan)) {
dbPesanan.forEach(o => {
const st = (o.status || "").toLowerCase();
if (st.includes("lunas") || st.includes("selesai") || st.includes("verifikasi") || st.includes("auto-acc")) {
const itemDetail = Array.isArray(o.items) && o.items.length > 0
? o.items.map(i => `${i.nama} (${i.qty}x)`).join(", ")
: "Pesanan Toko";
daftarMutasi.push({
tag: "Barang Keluar (Penjualan)",
judul: `Pesanan #${o.orderId || '-'}`,
sub: `Pelanggan: ${o.customerName || 'Pembeli'} | Menu: ${itemDetail}`,
nominal: Number(o.total) || 0,
tipe: "masuk",
waktu: o.metodeBayar || "Penjualan Toko"
});
}
});
}
// Mutasi Keluar: Pembelian Stok / Barang Masuk dari Pemasok
if (Array.isArray(dbSupplierPengiriman)) {
dbSupplierPengiriman.forEach(s => {
if ((s.status || "").toLowerCase() === "diterima") {
const qty = Number(s.jumlahStok) || 0;
const harga = Number(s.hargaBeli) || 0;
const totalBeli = qty * harga;
daftarMutasi.push({
tag: "Barang Masuk (Pasokan)",
judul: `Pasokan #${s.resi || '-'}`,
sub: `Pemasok: ${s.namaSupplier || '-'} | Barang: ${s.namaBarang || '-'} (+${qty} unit @ Rp ${harga.toLocaleString("id-ID")})`,
nominal: totalBeli,
tipe: "keluar",
waktu: "Dipotong Kas Toko"
});
}
});
}
// Mutasi Kas Tambahan (Penarikan / Setoran)
if (Array.isArray(dbMutasiTambahan)) {
dbMutasiTambahan.forEach(m => {
daftarMutasi.push({
tag: m.tipe === "masuk" ? "Setoran Kas" : "Penarikan Kas",
judul: m.judul || "Penyesuaian Saldo",
sub: m.sub || "-",
nominal: Number(m.nominal) || 0,
tipe: m.tipe || "keluar",
waktu: m.waktu || "-"
});
});
}
// Filter Kategori (Semua / Masuk / Keluar)
if (filterAktif === 'in') {
daftarMutasi = daftarMutasi.filter(m => m.tipe === 'masuk');
} else if (filterAktif === 'out') {
daftarMutasi = daftarMutasi.filter(m => m.tipe === 'keluar');
}
// Filter Kata Kunci Pencarian
const keyword = (document.getElementById("cari-mutasi")?.value || "").toLowerCase().trim();
if (keyword) {
daftarMutasi = daftarMutasi.filter(m =>
m.judul.toLowerCase().includes(keyword) ||
m.sub.toLowerCase().includes(keyword) ||
m.tag.toLowerCase().includes(keyword)
);
}
if (daftarMutasi.length === 0) {
mutasiContainer.innerHTML = `<p style="font-size:12px; color:#888; text-align:center; padding:16px;">Tidak ada riwayat mutasi transaksi.</p>`;
return;
}
daftarMutasi.reverse().forEach(m => {
const isMasuk = m.tipe === "masuk";
const warna = isMasuk ? "var(--success)" : "var(--danger)";
const tanda = isMasuk ? "+" : "-";
const badgeClass = isMasuk ? "badge-in" : "badge-out";
mutasiContainer.innerHTML += `
<div class="mutasi-item">
<div>
<span class="badge-tag ${badgeClass}">${m.tag}</span><br>
<strong>${m.judul}</strong><br>
<small style="color:#555; font-size:11px;">${m.sub}</small>
</div>
<div style="text-align: right; white-space: nowrap;">
<strong style="color: ${warna}; font-size: 13px;">${tanda} Rp ${m.nominal.toLocaleString("id-ID")}</strong><br>
<small style="color:#999; font-size: 10px;">${m.waktu}</small>
</div>
</div>
`;
});
}
function bersihkanMutasiKas() {
if (confirm("Kosongkan catatan penarikan dan setoran kas manual? (Transaksi penjualan & barang pasokan tetap tersimpan aman)")) {
setStorage("db_mutasi_mbal", []);
muatDataRekening();
alert("Catatan kas manual berhasil dibersihkan!");
}
}
function isiSemuaSaldo() {
const saldoSaiki = hitungTotalSaldo();
if (saldoSaiki <= 0) {
alert("Saldo kas toko saat ini kosong atau minus, tidak ada saldo yang bisa ditarik.");
return;
}
document.getElementById("tarik-nominal").value = saldoSaiki;
}
function gantiKategoriTujuan() {
const tujuan = document.getElementById("tarik-bank").value;
const boxNorek = document.getElementById("box-norek-tujuan");
const inputNorek = document.getElementById("tarik-norek");
if (tujuan === "Tunai Fisik") {
boxNorek.classList.add("hidden");
inputNorek.removeAttribute("required");
} else {
boxNorek.classList.remove("hidden");
inputNorek.setAttribute("required", "true");
}
}
function handleTarikSaldo(e) {
e.preventDefault();
const nominal = parseInt(document.getElementById("tarik-nominal").value, 10);
const bankTujuan = document.getElementById("tarik-bank").value;
const norekTujuan = document.getElementById("tarik-norek").value.trim();
const saldoSaiki = hitungTotalSaldo();
if (nominal > saldoSaiki) {
alert(`Penarikan gagal! Saldo kas toko saat ini hanya Rp ${saldoSaiki.toLocaleString("id-ID")}.`);
return;
}
const keteranganTujuan = bankTujuan === "Tunai Fisik"
? "Penarikan Tunai Fisik Kasir"
: `Transfer ke ${bankTujuan} (${norekTujuan})`;
const dbMutasiTambahan = getStorage("db_mutasi_mbal", []);
dbMutasiTambahan.push({
judul: "Penarikan Kas Toko",
sub: keteranganTujuan,
nominal: nominal,
tipe: "keluar",
waktu: new Date().toLocaleDateString("id-ID")
});
setStorage("db_mutasi_mbal", dbMutasiTambahan);
alert(`Penarikan sebesar Rp ${nominal.toLocaleString("id-ID")} berhasil diproses ke ${keteranganTujuan}!`);
tutupModal();
muatDataRekening();
}
function handleTopupKas(e) {
e.preventDefault();
const nominal = parseInt(document.getElementById("topup-nominal").value, 10);
const ket = document.getElementById("topup-ket").value.trim();
const dbMutasiTambahan = getStorage("db_mutasi_mbal", []);
dbMutasiTambahan.push({
judul: "Penyetoran Kas Toko",
sub: ket,
nominal: nominal,
tipe: "masuk",
waktu: new Date().toLocaleDateString("id-ID")
});
setStorage("db_mutasi_mbal", dbMutasiTambahan);
alert(`Kas toko berhasil ditambah sebesar Rp ${nominal.toLocaleString("id-ID")}!`);
tutupModal();
muatDataRekening();
}
function salinTeks(txt) {
navigator.clipboard.writeText(txt).then(() => {
alert("Nomor rekening berhasil disalin: " + txt);
});
}
function salinSemuaRekening() {
const data = "BCA: 876543210 (a/n Warung Amba)\nMandiri: 142000987654 (a/n Warung Amba)\nNMID QRIS: ID1020304050607";
navigator.clipboard.writeText(data).then(() => {
alert("Seluruh data rekening penampungan berhasil disalin!");
});
}
function bukaModalTransfer() {
document.getElementById("tarik-nominal").value = "";
document.getElementById("tarik-norek").value = "";
gantiKategoriTujuan();
document.getElementById("modal-transfer").classList.remove("hidden");
}
function bukaModalTopup() {
document.getElementById("topup-nominal").value = "";
document.getElementById("topup-ket").value = "";
document.getElementById("modal-topup").classList.remove("hidden");
}
function tutupModal() {
document.getElementById("modal-transfer").classList.add("hidden");
document.getElementById("modal-topup").classList.add("hidden");
}
</script>
</body>
</html>