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
+1531
View File
File diff suppressed because it is too large Load Diff
+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>
+476
View File
@@ -0,0 +1,476 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portal Resmi Pemasok & Pengiriman Barang</title>
<style>
:root {
--primary: #0288d1;
--primary-dark: #01579b;
--bg: #f4f9fd;
--border: #cfe2ff;
--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: 20px 14px; }
.hidden { display: none !important; }
.container { max-width: 900px; margin: 0 auto; }
.card { background: #fff; border-radius: 14px; padding: 22px; border: 1px solid var(--border); box-shadow: 0 4px 14px rgba(2,136,209,0.06); margin-bottom: 20px; }
.header-box { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid var(--primary); padding-bottom: 12px; margin-bottom: 16px; flex-wrap: wrap; gap: 8px; }
.form-group { margin-bottom: 12px; }
.form-group label { display: block; font-size: 13px; font-weight: bold; margin-bottom: 4px; }
.form-control { width: 100%; padding: 10px; border: 1.5px solid #ccc; border-radius: 6px; font-size: 14px; }
.form-control:focus { outline: none; border-color: var(--primary); }
.btn { padding: 9px 16px; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.btn-block { width: 100%; }
.btn-primary { background: var(--primary); color: #fff; }
.btn-success { background: var(--success); color: #fff; }
.btn-secondary { background: #eee; color: #333; font-size: 12px; padding: 6px 12px; }
.auth-box { max-width: 420px; margin: 50px auto; }
.auth-tabs { display: flex; gap: 8px; margin-bottom: 16px; border-bottom: 2px solid #eee; }
.auth-tab { flex: 1; padding: 10px; background: none; border: none; font-weight: bold; cursor: pointer; color: #777; }
.auth-tab.active { color: var(--primary); border-bottom: 3px solid var(--primary); margin-bottom: -2px; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 13px; min-width: 600px; }
th, td { padding: 10px; border: 1px solid #dee2e6; text-align: left; }
th { background: #e7f1ff; }
.pill { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: bold; }
.pill-kirim { background: #fff3cd; color: #856404; }
.pill-terima { background: #d1e7dd; color: #0f5132; }
.badge-juragan { background: var(--purple); color: #fff; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<!-- ================= MASUK & DAFTAR PEMASOK ================= -->
<div id="page-supplier-auth" class="card auth-box">
<div style="text-align: center; margin-bottom: 14px;">
<h2 style="color: var(--primary); margin-bottom: 4px;">🚚 Portal Pemasok</h2>
<small style="color: #666;">Mitra Pemasok, Kurir & Akses Pemilik Toko</small>
</div>
<div class="auth-tabs">
<button class="auth-tab active" id="tab-sup-login" onclick="gantiTabSupAuth('login')">Masuk</button>
<button class="auth-tab" id="tab-sup-reg" onclick="gantiTabSupAuth('reg')">Daftar Mitra</button>
</div>
<form id="form-sup-login" onsubmit="handleLoginSupplier(event)">
<div class="form-group">
<label>Nama Pengguna (Mitra atau Pemilik)</label>
<input type="text" id="log-sup-user" class="form-control" placeholder="Username pemasok / pemilik" required>
</div>
<div class="form-group">
<label>Kata Sandi</label>
<input type="password" id="log-sup-pass" class="form-control" placeholder="Kata sandi" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Masuk ke Portal</button>
<div id="sup-auth-msg" style="margin-top: 10px; color: var(--danger); font-size: 13px; text-align: center; font-weight: bold;"></div>
<div style="text-align: center; margin-top: 14px;">
<a href="javascript:void(0)" onclick="bukaModalResetSupplier()" style="font-size: 12px; color: var(--primary); text-decoration: none; font-weight: 600;">Lupa Kata Sandi Pemasok?</a>
</div>
</form>
<form id="form-sup-reg" class="hidden" onsubmit="handleRegisterSupplier(event)">
<div class="form-group">
<label>Nama Perusahaan / PT / Distributor</label>
<input type="text" id="reg-sup-nama" class="form-control" placeholder="Contoh: CV Sumber Makmur" required>
</div>
<div class="form-group">
<label>Nomor Kontak / WhatsApp</label>
<input type="text" id="reg-sup-hp" class="form-control" placeholder="0812-xxxx-xxxx" required>
</div>
<div class="form-group">
<label>Nama Pengguna</label>
<input type="text" id="reg-sup-user" class="form-control" placeholder="Username akun" required>
</div>
<div class="form-group">
<label>Kata Sandi</label>
<input type="password" id="reg-sup-pass" class="form-control" placeholder="Kata sandi akun" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Daftar Sebagai Mitra</button>
</form>
<div style="text-align: center; margin-top: 16px; border-top: 1px dashed #ddd; padding-top: 12px;">
<a href="index.html" style="font-size: 12px; color: #555; text-decoration: none;">← Kembali ke Halaman Utama Toko</a>
</div>
</div>
<!-- ================= DASHBOARD PEMASOK ================= -->
<div id="page-supplier-dashboard" class="hidden">
<div class="card">
<div class="header-box">
<div>
<h2 style="color: var(--primary); margin: 0;">🚚 Formulir Surat Jalan & Pengiriman Barang</h2>
<div style="margin-top: 4px;">
<span id="supplier-info-label" style="color: #555; font-weight: bold;"></span>
<span id="label-mode-juragan" class="badge-juragan hidden">👑 Akses Pemilik Toko</span>
</div>
</div>
<div style="display: flex; gap: 8px;">
<a href="index.html" class="btn-secondary">← Kembali ke Toko</a>
<button class="btn btn-secondary" onclick="logoutSupplier()">Keluar</button>
</div>
</div>
<form onsubmit="handleKirimBarang(event)">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px;">
<div class="form-group">
<label>Nama Mitra / Pemasok</label>
<input type="text" id="kirim-sup-nama" class="form-control" required>
</div>
<div class="form-group">
<label>Nomor Kontak</label>
<input type="text" id="kirim-sup-hp" class="form-control" required>
</div>
<div class="form-group">
<label>Nama Barang yang Dikirim</label>
<input type="text" id="kirim-barang" class="form-control" placeholder="Contoh: Kopi Hitam Gula Aren" required>
</div>
<div class="form-group">
<label>Jumlah Pasokan (Unit / Dus)</label>
<input type="number" id="kirim-qty" class="form-control" placeholder="Contoh: 50" min="1" required>
</div>
<div class="form-group">
<label>Harga Beli Satuan (Rp)</label>
<input type="number" id="kirim-harga" class="form-control" placeholder="Contoh: 8000" required>
</div>
<div class="form-group">
<label>No. Plat Kendaraan / Nama Kurir</label>
<input type="text" id="kirim-kurir" class="form-control" placeholder="B 1234 ABC (Pak Doni)" required>
</div>
</div>
<button type="submit" class="btn btn-primary" style="margin-top: 10px;">🚀 Catat & Kirim Data Pasokan</button>
</form>
</div>
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<h3 id="table-title">📦 Riwayat Surat Jalan & Pengiriman</h3>
<span id="filter-keterangan" style="font-size: 12px; color: #666;"></span>
</div>
<div style="overflow-x: auto;">
<table>
<thead>
<tr>
<th>No. Resi</th>
<th>Pemasok / Sales</th>
<th>Nama Barang</th>
<th>Jumlah</th>
<th>Total Tagihan</th>
<th>Kurir</th>
<th>Status</th>
<th id="kolom-aksi-juragan">Aksi</th>
</tr>
</thead>
<tbody id="supplier-table-body"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal Reset Password Supplier -->
<div id="modal-reset-sup" class="modal-overlay hidden">
<div class="modal-content">
<h3 style="color: var(--primary); margin-bottom: 10px;">🔑 Reset Kata Sandi Pemasok</h3>
<form onsubmit="handleResetPasswordSupplier(event)">
<div class="form-group">
<label>Nama Pengguna (Username)</label>
<input type="text" id="reset-sup-user" class="form-control" placeholder="Masukkan username pemasok" required>
</div>
<div class="form-group">
<label>Kata Sandi Baru</label>
<input type="password" id="reset-sup-pass" class="form-control" placeholder="Masukkan kata sandi baru" required>
</div>
<div class="form-group">
<label>Konfirmasi Kata Sandi Baru</label>
<input type="password" id="reset-sup-confirm" class="form-control" placeholder="Ulangi kata sandi baru" required>
</div>
<div style="display: flex; gap: 8px; margin-top: 14px;">
<button type="button" class="btn btn-secondary" style="width: 50%;" onclick="tutupModalResetSupplier()">Batal</button>
<button type="submit" class="btn btn-primary" style="width: 50%;">Simpan Sandi</button>
</div>
</form>
</div>
</div>
<script>
function bukaModalResetSupplier() {
document.getElementById("modal-reset-sup").classList.remove("hidden");
}
function tutupModalResetSupplier() {
document.getElementById("modal-reset-sup").classList.add("hidden");
}
function handleResetPasswordSupplier(e) {
e.preventDefault();
const username = document.getElementById("reset-sup-user").value.trim();
const sandiBaru = document.getElementById("reset-sup-pass").value;
const konfirmasi = document.getElementById("reset-sup-confirm").value;
if (sandiBaru !== konfirmasi) {
return alert("Kata sandi baru dan konfirmasi tidak cocok!");
}
let dbAkunSupplier = getStorage("db_akun_supplier", []);
const akun = dbAkunSupplier.find(s => s.user.toLowerCase() === username.toLowerCase());
if (!akun) {
return alert("Username pemasok tidak ditemukan dalam sistem!");
}
akun.pass = sandiBaru;
setStorage("db_akun_supplier", dbAkunSupplier);
alert(`Kata sandi akun @${username} berhasil diperbarui! Silakan masuk.`);
tutupModalResetSupplier();
e.target.reset();
}
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 dbAkunSupplier = getStorage("db_akun_supplier", [
{ nama: "CV Sumber Kopi Abadi", hp: "0812-3456-7890", user: "supplier", pass: "123" }
]);
let dbSupplierPengiriman = getStorage("db_supplier_pengiriman", []);
let dbAkunToko = getStorage("db_akun", []);
let dbBarangToko = getStorage("db_barang", []);
let sesiSupplier = getStorage("aktif_supplier", null);
let sesiToko = getStorage("aktif_user", null);
window.onload = function() {
if (sesiToko && sesiToko.role === "admin") {
sesiSupplier = {
nama: sesiToko.name,
hp: "Kantor Pusat",
user: sesiToko.user,
role: "admin"
};
setStorage("aktif_supplier", sesiSupplier);
bukaDashboardSupplier();
return;
}
if (sesiSupplier) {
bukaDashboardSupplier();
} else {
document.getElementById("page-supplier-auth").classList.remove("hidden");
document.getElementById("page-supplier-dashboard").classList.add("hidden");
}
};
function gantiTabSupAuth(tab) {
document.getElementById("tab-sup-login").classList.toggle("active", tab === 'login');
document.getElementById("tab-sup-reg").classList.toggle("active", tab === 'reg');
document.getElementById("form-sup-login").classList.toggle("hidden", tab !== 'login');
document.getElementById("form-sup-reg").classList.toggle("hidden", tab !== 'reg');
document.getElementById("sup-auth-msg").innerText = "";
}
function handleRegisterSupplier(e) {
e.preventDefault();
const nama = document.getElementById("reg-sup-nama").value.trim();
const hp = document.getElementById("reg-sup-hp").value.trim();
const user = document.getElementById("reg-sup-user").value.trim();
const pass = document.getElementById("reg-sup-pass").value;
if (dbAkunSupplier.some(s => s.user.toLowerCase() === user.toLowerCase())) {
return document.getElementById("sup-auth-msg").innerText = "Nama pengguna sudah digunakan!";
}
dbAkunSupplier.push({ nama, hp, user, pass, role: "supplier" });
setStorage("db_akun_supplier", dbAkunSupplier);
alert("Akun mitra berhasil didaftarkan! Silakan masuk.");
gantiTabSupAuth('login');
document.getElementById("form-sup-reg").reset();
}
function handleLoginSupplier(e) {
e.preventDefault();
const user = document.getElementById("log-sup-user").value.trim();
const pass = document.getElementById("log-sup-pass").value;
dbAkunToko = getStorage("db_akun", []);
const juraganMatch = dbAkunToko.find(u => u.user === user && u.pass === pass && u.role === "admin");
if (juraganMatch) {
sesiSupplier = {
nama: juraganMatch.name,
hp: "Kantor Pemilik",
user: juraganMatch.user,
role: "admin"
};
setStorage("aktif_supplier", sesiSupplier);
bukaDashboardSupplier();
return;
}
const match = dbAkunSupplier.find(s => s.user === user && s.pass === pass);
if (!match) {
return document.getElementById("sup-auth-msg").innerText = "Nama pengguna atau kata sandi salah!";
}
sesiSupplier = { ...match, role: "supplier" };
setStorage("aktif_supplier", sesiSupplier);
bukaDashboardSupplier();
}
function logoutSupplier() {
sesiSupplier = null;
localStorage.removeItem("aktif_supplier");
document.getElementById("page-supplier-dashboard").classList.add("hidden");
document.getElementById("page-supplier-auth").classList.remove("hidden");
}
function bukaDashboardSupplier() {
document.getElementById("page-supplier-auth").classList.add("hidden");
document.getElementById("page-supplier-dashboard").classList.remove("hidden");
const isJuragan = sesiSupplier.role === "admin";
document.getElementById("label-mode-juragan").classList.toggle("hidden", !isJuragan);
if (isJuragan) {
document.getElementById("supplier-info-label").innerText = `Masuk sebagai: Pemilik Toko (${sesiSupplier.nama})`;
document.getElementById("kirim-sup-nama").value = "Pusat / Pemilik Toko";
document.getElementById("kirim-sup-hp").value = "0811-Admin-Pusat";
document.getElementById("kirim-sup-nama").removeAttribute("disabled");
document.getElementById("kirim-sup-hp").removeAttribute("disabled");
document.getElementById("table-title").innerText = "📦 Semua Pengiriman Pemasok (Akses Pemilik)";
document.getElementById("filter-keterangan").innerText = "Pemilik dapat langsung menerima dan memverifikasi barang.";
} else {
document.getElementById("supplier-info-label").innerText = `Mitra: ${sesiSupplier.nama} (@${sesiSupplier.user})`;
document.getElementById("kirim-sup-nama").value = sesiSupplier.nama;
document.getElementById("kirim-sup-hp").value = sesiSupplier.hp;
document.getElementById("kirim-sup-nama").setAttribute("disabled", "true");
document.getElementById("kirim-sup-hp").setAttribute("disabled", "true");
document.getElementById("table-title").innerText = "📦 Riwayat Pengiriman Saya";
document.getElementById("filter-keterangan").innerText = "Hanya menampilkan data pengiriman akun ini.";
}
renderTableKiriman();
}
function renderTableKiriman() {
const tbody = document.getElementById("supplier-table-body");
tbody.innerHTML = "";
dbSupplierPengiriman = getStorage("db_supplier_pengiriman", []);
const isJuragan = sesiSupplier.role === "admin";
const dataDitampilake = isJuragan
? dbSupplierPengiriman
: dbSupplierPengiriman.filter(s => s.supplierUser === sesiSupplier.user);
if (dataDitampilake.length === 0) {
tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;">Belum ada riwayat pengiriman barang.</td></tr>`;
return;
}
dataDitampilake.slice().reverse().forEach(s => {
let pill = s.status === "Diterima" ? "pill-terima" : "pill-kirim";
let total = parseInt(s.jumlahStok, 10) * parseInt(s.hargaBeli, 10);
let aksiHtml = "-";
if (isJuragan) {
aksiHtml = s.status === "Dikirim"
? `<button class="btn btn-success btn-sm" onclick="terimaKirimanLangsung(${s.id})">Terima & Tambah Stok</button>`
: `<span style="color:green; font-weight:bold;">✓ Selesai</span>`;
} else {
aksiHtml = s.status === "Diterima" ? `<span style="color:green;">✓ Diterima</span>` : `⏳ Menunggu Verifikasi`;
}
tbody.innerHTML += `
<tr>
<td><code>#${s.resi}</code></td>
<td><strong>${s.namaSupplier}</strong><br><small>${s.salesHp}</small></td>
<td>${s.namaBarang}</td>
<td><strong>${s.jumlahStok}</strong> unit</td>
<td>Rp ${total.toLocaleString("id-ID")}</td>
<td>${s.kurir}</td>
<td><span class="pill ${pill}">${s.status === 'Diterima' ? '✓ Diterima Pemilik' : '⏳ Dalam Pengiriman'}</span></td>
<td>${aksiHtml}</td>
</tr>
`;
});
}
function handleKirimBarang(e) {
e.preventDefault();
const resiBaru = "SJ-" + Math.floor(100000 + Math.random() * 900000);
const dataKiriman = {
id: Date.now(),
resi: resiBaru,
supplierUser: sesiSupplier.user,
namaSupplier: document.getElementById("kirim-sup-nama").value.trim(),
salesHp: document.getElementById("kirim-sup-hp").value.trim(),
namaBarang: document.getElementById("kirim-barang").value.trim(),
jumlahStok: parseInt(document.getElementById("kirim-qty").value, 10),
hargaBeli: parseInt(document.getElementById("kirim-harga").value, 10),
kurir: document.getElementById("kirim-kurir").value.trim(),
status: "Dikirim"
};
dbSupplierPengiriman.push(dataKiriman);
setStorage("db_supplier_pengiriman", dbSupplierPengiriman);
alert(`Pengiriman berhasil dicatat dengan Resi #${resiBaru}!`);
document.getElementById("kirim-barang").value = "";
document.getElementById("kirim-qty").value = "";
document.getElementById("kirim-harga").value = "";
document.getElementById("kirim-kurir").value = "";
renderTableKiriman();
}
function terimaKirimanLangsung(id) {
dbBarangToko = getStorage("db_barang", []);
dbSupplierPengiriman = getStorage("db_supplier_pengiriman", []);
const s = dbSupplierPengiriman.find(x => x.id === id);
if (!s) return;
const totalBiaya = parseInt(s.jumlahStok, 10) * parseInt(s.hargaBeli, 10);
let b = dbBarangToko.find(x => x.nama.toLowerCase() === s.namaBarang.toLowerCase());
if (b) {
b.stok += parseInt(s.jumlahStok, 10);
} else {
dbBarangToko.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", dbSupplierPengiriman);
setStorage("db_barang", dbBarangToko);
renderTableKiriman();
alert(`Pengiriman #${s.resi} berhasil diterima!\n- Stok "${s.namaBarang}" bertambah ${s.jumlahStok} unit.\n- Biaya pembelian Rp ${totalBiaya.toLocaleString("id-ID")} otomatis memotong saldo kas bank toko.`);
}
</script>
</body>
</html>