<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>
📱 SIM Card Sales Tracker
Input data penjualan + hitung otomatis laba/rugi (Pendapatan = Penjualan - Modal)