Skip to content

SIMCARD #1

Description

@ONIMARUSEKAI
<title>Sistem Penjualan SIM Card | Pencatatan & Laba</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: system-ui, 'Segoe UI', 'Inter', 'Roboto', sans-serif; }
    body {
        background: linear-gradient(145deg, #e9f0fc 0%, #d9e4f5 100%);
        min-height: 100vh;
        padding: 2rem 1.5rem;
    }

    /* container utama */
    .app-container {
        max-width: 1400px;
        margin: 0 auto;
    }

    /* header */
    .header {
        text-align: center;
        margin-bottom: 2rem;
    }

    .header h1 {
        font-size: 1.9rem;
        background: linear-gradient(135deg, #1A2A4F, #0F2B3D);
        background-clip: text;
        -webkit-background-clip: text;
        color: transparent;
        letter-spacing: -0.3px;
        font-weight: 700;
    }

    .header p {
        color: #2c3e66;
        margin-top: 8px;
        font-weight: 500;
        background: rgba(255,255,255,0.6);
        display: inline-block;
        padding: 4px 18px;
        border-radius: 40px;
        backdrop-filter: blur(2px);
    }

    /* dua kolom: form kiri + daftar kanan */
    .dashboard-grid {
        display: flex;
        flex-wrap: wrap;
        gap: 28px;
    }

    /* panel form input */
    .form-panel {
        flex: 1.2;
        min-width: 300px;
        background: rgba(255,255,255,0.92);
        backdrop-filter: blur(2px);
        border-radius: 2rem;
        padding: 1.6rem 1.8rem;
        box-shadow: 0 20px 35px -12px rgba(0,0,0,0.2);
        border: 1px solid rgba(255,255,255,0.5);
        transition: all 0.2s;
    }

    /* ringkasan total (modal, penjualan, pendapatan) */
    .summary-panel {
        flex: 0.9;
        min-width: 280px;
        background: rgba(255,255,255,0.92);
        backdrop-filter: blur(2px);
        border-radius: 2rem;
        padding: 1.5rem 1.8rem;
        box-shadow: 0 20px 35px -12px rgba(0,0,0,0.2);
        border: 1px solid rgba(255,255,255,0.6);
    }

    /* tabel riwayat */
    .history-section {
        margin-top: 2rem;
        background: rgba(255,255,255,0.96);
        border-radius: 1.8rem;
        padding: 1.2rem 0 0.2rem 0;
        box-shadow: 0 12px 30px rgba(0,0,0,0.1);
        overflow-x: auto;
    }

    /* form styling */
    .form-group {
        margin-bottom: 1.25rem;
        display: flex;
        flex-direction: column;
    }

    .form-group label {
        font-weight: 600;
        margin-bottom: 6px;
        color: #1e2a47;
        display: flex;
        align-items: center;
        gap: 8px;
    }

    .form-group label i {
        font-weight: normal;
        font-size: 1.1rem;
    }

    input, select {
        padding: 12px 14px;
        border-radius: 20px;
        border: 1.5px solid #cfdfed;
        background: white;
        font-size: 0.95rem;
        transition: 0.2s;
        outline: none;
    }

    input:focus, select:focus {
        border-color: #2c6e9e;
        box-shadow: 0 0 0 3px rgba(44,110,158,0.2);
    }

    .btn-primary {
        background: linear-gradient(100deg, #1f6392, #123e60);
        border: none;
        padding: 12px 18px;
        border-radius: 40px;
        font-weight: bold;
        font-size: 1rem;
        color: white;
        cursor: pointer;
        transition: 0.2s;
        width: 100%;
        margin-top: 10px;
        box-shadow: 0 5px 12px rgba(0,0,0,0.1);
    }

    .btn-primary:hover {
        background: linear-gradient(100deg, #0f4b72, #0a2e48);
        transform: scale(0.98);
        box-shadow: 0 2px 6px rgba(0,0,0,0.1);
    }

    .btn-reset {
        background: #eef2f7;
        color: #2c4e6e;
        margin-top: 8px;
        border: 1px solid #cbdde9;
    }

    .btn-reset:hover {
        background: #e2e8f0;
    }

    /* kartu total */
    .stat-card {
        background: #f0f6fe;
        border-radius: 1.5rem;
        padding: 1rem 1.2rem;
        margin-bottom: 1.2rem;
        border-left: 6px solid;
        transition: 0.1s;
    }

    .stat-card.modal {
        border-left-color: #f4b942;
        background: #fff6e8;
    }
    .stat-card.penjualan {
        border-left-color: #2c9e6e;
        background: #e9f9f0;
    }
    .stat-card.pendapatan {
        border-left-color: #2c6e9e;
        background: #e8f0fe;
    }

    .stat-label {
        font-size: 0.85rem;
        text-transform: uppercase;
        letter-spacing: 1px;
        font-weight: 700;
        color: #2c3e66;
    }

    .stat-value {
        font-size: 2rem;
        font-weight: 800;
        color: #1a2c3e;
        word-break: break-word;
    }

    .sub-info {
        font-size: 0.7rem;
        color: #5f6c86;
        margin-top: 4px;
    }

    /* tabel */
    .data-table {
        width: 100%;
        border-collapse: collapse;
        font-size: 0.85rem;
    }

    .data-table th {
        background: #eef3fc;
        padding: 14px 12px;
        text-align: left;
        font-weight: 700;
        color: #1f4569;
        border-bottom: 2px solid #cbdbe2;
    }

    .data-table td {
        padding: 12px 12px;
        border-bottom: 1px solid #e2e8f0;
        vertical-align: middle;
    }

    .data-table tr:hover {
        background: #f8fafd;
    }

    .delete-btn {
        background: none;
        border: none;
        color: #b91c1c;
        font-size: 1.3rem;
        cursor: pointer;
        padding: 4px 10px;
        border-radius: 30px;
        transition: 0.2s;
        font-weight: bold;
    }

    .delete-btn:hover {
        background: #ffe5e5;
        transform: scale(1.05);
    }

    .empty-row td {
        text-align: center;
        padding: 2rem;
        color: #6c7a8e;
        font-style: italic;
    }

    .badge-quantity {
        background: #e0ebf5;
        padding: 4px 8px;
        border-radius: 30px;
        font-weight: 600;
        display: inline-block;
        text-align: center;
        min-width: 45px;
    }

    footer {
        text-align: center;
        margin-top: 2rem;
        font-size: 0.75rem;
        color: #345;
        opacity: 0.7;
    }

    @media (max-width: 760px) {
        body {
            padding: 1rem;
        }
        .stat-value {
            font-size: 1.5rem;
        }
        .data-table th, .data-table td {
            padding: 8px 6px;
            font-size: 0.75rem;
        }
    }
</style>

📱 SIM Card Sales Tracker

Input data penjualan + hitung otomatis laba/rugi (Pendapatan = Penjualan - Modal)

<div class="dashboard-grid">
    <!-- FORM INPUT -->
    <div class="form-panel">
        <h3 style="margin-bottom: 1rem; display: flex; gap: 8px;">✍️ Tambah Transaksi</h3>
        <form id="salesForm">
            <div class="form-group">
                <label>🏷️ Nama / Jenis SIM Card</label>
                <input type="text" id="simName" placeholder="Contoh: Telkomsel 5GB / XL Axiata" required>
            </div>
            <div class="form-group">
                <label>📦 Quantity (Jumlah)</label>
                <input type="number" id="quantity" placeholder="Jumlah kartu" min="1" value="1" required>
            </div>
            <div class="form-group">
                <label>💰 Modal per Unit (Rp)</label>
                <input type="number" id="modalUnit" placeholder="Harga beli per sim" min="0" step="100" required>
            </div>
            <div class="form-group">
                <label>💵 Harga Jual per Unit (Rp)</label>
                <input type="number" id="priceUnit" placeholder="Harga jual per sim" min="0" step="100" required>
            </div>
            <div class="form-group">
                <label>📅 Tanggal Penjualan</label>
                <input type="date" id="transactionDate" required>
            </div>
            <button type="submit" class="btn-primary">➕ Simpan Data Penjualan</button>
            <button type="button" id="resetFormBtn" class="btn-primary btn-reset">⟳ Reset Form</button>
        </form>
        <div style="margin-top: 15px; font-size: 0.7rem; background:#f2f6fb; padding:8px 12px; border-radius: 28px;">
            💡 Pendapatan per transaksi = (Harga Jual × Quantity) - (Modal × Quantity)<br>
            Total pendapatan keseluruhan = Total Penjualan - Total Modal
        </div>
    </div>

    <!-- RINGKASAN TOTAL MODAL, PENJUALAN, PENDAPATAN -->
    <div class="summary-panel">
        <h3 style="margin-bottom: 1rem; font-size: 1.4rem;">📊 Ringkasan</h3>
        <div class="stat-card modal">
            <div class="stat-label">📥 TOTAL MODAL (Keseluruhan)</div>
            <div class="stat-value" id="totalModalDisplay">Rp 0</div>
            <div class="sub-info">Σ (Modal per unit × Quantity)</div>
        </div>
        <div class="stat-card penjualan">
            <div class="stat-label">💸 TOTAL PENJUALAN</div>
            <div class="stat-value" id="totalPenjualanDisplay">Rp 0</div>
            <div class="sub-info">Σ (Harga Jual × Quantity)</div>
        </div>
        <div class="stat-card pendapatan">
            <div class="stat-label">📈 TOTAL PENDAPATAN (Laba/Rugi)</div>
            <div class="stat-value" id="totalPendapatanDisplay">Rp 0</div>
            <div class="sub-info">Pendapatan = Penjualan - Modal</div>
        </div>
    </div>
</div>

<!-- TABEL RIWAYAT PENJUALAN -->
<div class="history-section">
    <div style="padding: 0 1.2rem 0.6rem 1.2rem;">
        <h3 style="display: inline-block;">📋 Data Penjualan SIM Card</h3>
        <span style="float: right; font-size: 0.7rem; background:#eef2f9; padding:4px 10px; border-radius: 20px;" id="rowCountBadge">0 transaksi</span>
    </div>
    <div style="overflow-x: auto;">
        <table class="data-table" id="salesTable">
            <thead>
                <tr>
                    <th>Tanggal</th><th>Nama SIM</th><th>Qty</th><th>Modal/Unit (Rp)</th><th>Jual/Unit (Rp)</th><th>Total Modal (Rp)</th><th>Total Jual (Rp)</th><th>Pendapatan (Rp)</th><th>Aksi</th>
                </tr>
            </thead>
            <tbody id="tableBody">
                <tr class="empty-row"><td colspan="9">Belum ada data. Silakan tambah transaksi penjualan.</td></tr>
            </tbody>
        </table>
    </div>
    <div style="padding: 12px 20px; display: flex; justify-content: flex-end; gap: 12px; border-top: 1px solid #e2edf5; margin-top: 8px;">
        <button id="clearAllBtn" style="background: #fee2e2; border: none; padding: 8px 16px; border-radius: 30px; font-weight: 600; color:#a12; cursor: pointer;">🗑 Hapus Semua Data</button>
    </div>
</div>
<footer>Pendapatan = Total Penjualan - Total Modal | Setiap transaksi tersimpan di lokal browser</footer>
<script> // ----- Data Array untuk menyimpan semua transaksi ----- let transactions = []; // Referensi DOM const form = document.getElementById('salesForm'); const simNameInput = document.getElementById('simName'); const quantityInput = document.getElementById('quantity'); const modalUnitInput = document.getElementById('modalUnit'); const priceUnitInput = document.getElementById('priceUnit'); const dateInput = document.getElementById('transactionDate'); const tableBody = document.getElementById('tableBody'); const totalModalSpan = document.getElementById('totalModalDisplay'); const totalPenjualanSpan = document.getElementById('totalPenjualanDisplay'); const totalPendapatanSpan = document.getElementById('totalPendapatanDisplay'); const rowCountBadge = document.getElementById('rowCountBadge'); const clearAllBtn = document.getElementById('clearAllBtn'); const resetFormBtn = document.getElementById('resetFormBtn'); // Helper: format Rupiah (tanpa desimal) function formatRupiah(amount) { if (amount === undefined || amount === null) amount = 0; return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(amount); } // Hitung ulang semua total (modal total, penjualan total, pendapatan total) function recalcTotals() { let totalModalAll = 0; let totalPenjualanAll = 0; transactions.forEach(t => { const modalTotalItem = t.modalUnit * t.quantity; const penjualanTotalItem = t.priceUnit * t.quantity; totalModalAll += modalTotalItem; totalPenjualanAll += penjualanTotalItem; }); const totalPendapatan = totalPenjualanAll - totalModalAll; totalModalSpan.innerText = formatRupiah(totalModalAll); totalPenjualanSpan.innerText = formatRupiah(totalPenjualanAll); totalPendapatanSpan.innerText = formatRupiah(totalPendapatan); // update badge jumlah transaksi const count = transactions.length; rowCountBadge.innerText = count + (count === 1 ? ' transaksi' : ' transaksi'); } // Render tabel berdasarkan data transactions function renderTable() { if (!tableBody) return; if (transactions.length === 0) { tableBody.innerHTML = `📭 Belum ada data penjualan. Silakan tambahkan transaksi.`; recalcTotals(); return; } let html = ''; // loop dan tampilkan semua data (urutkan berdasarkan tanggal terbaru? optional: urut descending) // biar sesuai urutan input (push terbaru di akhir) => tampilkan terbaru di atas? lebih rapi descending index // supaya yang baru input lebih mudah dilihat, kita tampilkan dari indeks terakhir ke awal (descending) const reversed = [...transactions].reverse(); reversed.forEach((item, idx) => { const originalIndex = transactions.length - 1 - idx; // untuk keperluan hapus dll const totalModalItem = item.modalUnit * item.quantity; const totalJualItem = item.priceUnit * item.quantity; const pendapatanItem = totalJualItem - totalModalItem; const pendapatanClass = pendapatanItem >= 0 ? '✅' : '⚠️'; const warnaPendapatan = pendapatanItem >= 0 ? 'style="color:#1f7840; font-weight:600;"' : 'style="color:#c0392b; font-weight:600;"'; html += ` ${escapeHtml(item.date)} ${escapeHtml(item.simName)} ${item.quantity} ${formatRupiah(item.modalUnit)} ${formatRupiah(item.priceUnit)} ${formatRupiah(totalModalItem)} ${formatRupiah(totalJualItem)} ${pendapatanClass} ${formatRupiah(pendapatanItem)} 🗑️ `; }); tableBody.innerHTML = html; // attach event delete ke setiap tombol hapus document.querySelectorAll('.delete-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.preventDefault(); const idx = parseInt(btn.getAttribute('data-idx')); if (!isNaN(idx) && idx >= 0 && idx < transactions.length) { transactions.splice(idx, 1); saveToLocalStorage(); renderTable(); recalcTotals(); } }); }); recalcTotals(); } // fungsi sederhana menghindari XSS function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(c) { return c; }); } // simpan data ke localStorage function saveToLocalStorage() { localStorage.setItem('simcardSalesData', JSON.stringify(transactions)); } // load dari localStorage function loadFromLocalStorage() { const stored = localStorage.getItem('simcardSalesData'); if (stored) { try { const parsed = JSON.parse(stored); if (Array.isArray(parsed)) { transactions = parsed; } else { transactions = []; } } catch(e) { transactions = []; } } else { // data demo / contoh supaya tidak kosong (optional beri beberapa sample) // memberikan contoh data awal agar user melihat fungsionalitas if (transactions.length === 0) { const today = new Date().toISOString().slice(0,10); transactions = [ { id: Date.now()+1, simName: "Telkomsel 10GB", quantity: 3, modalUnit: 45000, priceUnit: 65000, date: today }, { id: Date.now()+2, simName: "XL Prioritas", quantity: 2, modalUnit: 38000, priceUnit: 55000, date: today }, { id: Date.now()+3, simName: "Indosat Ooredoo", quantity: 1, modalUnit: 30000, priceUnit: 49000, date: today } ]; saveToLocalStorage(); } } renderTable(); } // Tambah transaksi baru function addTransaction(event) { event.preventDefault(); // ambil value let simName = simNameInput.value.trim(); let quantity = parseInt(quantityInput.value); let modalUnit = parseFloat(modalUnitInput.value); let priceUnit = parseFloat(priceUnitInput.value); let date = dateInput.value; // validasi if (!simName) { alert("Nama SIM Card harus diisi!"); return; } if (isNaN(quantity) || quantity < 1) { alert("Quantity minimal 1"); return; } if (isNaN(modalUnit) || modalUnit < 0) { alert("Modal per unit harus angka positif atau 0"); return; } if (isNaN(priceUnit) || priceUnit < 0) { alert("Harga jual per unit harus angka positif atau 0"); return; } if (!date) { alert("Pilih tanggal penjualan"); return; } // buat object transaksi const newTransaction = { id: Date.now() + Math.random(), simName: simName, quantity: quantity, modalUnit: modalUnit, priceUnit: priceUnit, date: date }; transactions.push(newTransaction); saveToLocalStorage(); renderTable(); // reset form (kecuali tanggal biarkan? lebih baik reset semua field untuk input cepat) resetFormFields(); // fokus ke nama simNameInput.focus(); } function resetFormFields() { simNameInput.value = ''; quantityInput.value = '1'; modalUnitInput.value = ''; priceUnitInput.value = ''; // set tanggal hari ini untuk kenyamanan const today = new Date().toISOString().slice(0,10); dateInput.value = today; } // hapus semua data function clearAllData() { if (confirm("⚠️ Hapus SEMUA data penjualan? Tindakan ini tidak bisa dibatalkan.")) { transactions = []; saveToLocalStorage(); renderTable(); recalcTotals(); } } // set default tanggal saat load halaman function setDefaultDate() { if (!dateInput.value) { const today = new Date().toISOString().slice(0,10); dateInput.value = today; } } // event listeners form.addEventListener('submit', addTransaction); clearAllBtn.addEventListener('click', clearAllData); resetFormBtn.addEventListener('click', (e) => { e.preventDefault(); resetFormFields(); }); // inisialisasi halaman function init() { setDefaultDate(); loadFromLocalStorage(); } init(); </script>

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions