ttup / admin.html
Phoe2004's picture
Upload 6 files
2b03906 verified
<!DOCTYPE html>
<html lang="my">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin - POS Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Myanmar:wght@300;400;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
:root {
--bg: #0a0e1a;
--card: #111827;
--card2: #1a2234;
--border: #1e293b;
--accent: #f59e0b;
--green: #10b981;
--red: #ef4444;
--blue: #3b82f6;
--purple: #8b5cf6;
--text: #f1f5f9;
--muted: #64748b;
}
body {
background: var(--bg);
color: var(--text);
font-family: 'Noto Sans Myanmar', sans-serif;
min-height: 100vh;
}
/* ── Sidebar + Layout ── */
.layout { display: flex; min-height: 100vh; }
.sidebar {
width: 220px;
background: var(--card);
border-right: 1px solid var(--border);
display: flex; flex-direction: column;
position: fixed; left: 0; top: 0; bottom: 0;
z-index: 50;
transition: transform 0.3s;
}
.sidebar-logo {
padding: 20px 16px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: 10px;
}
.logo-icon {
width: 40px; height: 40px;
background: linear-gradient(135deg, var(--accent), #d97706);
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 18px; flex-shrink: 0;
}
.sidebar-logo h1 { font-size: 14px; font-weight: 700; line-height: 1.3; }
.sidebar-logo p { font-size: 10px; color: var(--muted); font-family: 'JetBrains Mono', monospace; }
.nav { padding: 12px 8px; flex: 1; }
.nav-item {
display: flex; align-items: center; gap: 10px;
padding: 11px 12px;
border-radius: 10px;
cursor: pointer;
color: var(--muted);
font-size: 13px;
transition: all 0.15s;
margin-bottom: 2px;
}
.nav-item:hover { background: var(--card2); color: var(--text); }
.nav-item.active { background: rgba(245,158,11,0.1); color: var(--accent); }
.nav-item .nav-icon { font-size: 16px; }
.sidebar-footer {
padding: 12px 8px;
border-top: 1px solid var(--border);
}
.user-card {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px;
background: var(--card2);
border-radius: 10px;
margin-bottom: 8px;
}
.user-avatar {
width: 32px; height: 32px;
background: linear-gradient(135deg, var(--purple), #7c3aed);
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 14px; flex-shrink: 0;
}
.user-card p { font-size: 12px; font-weight: 600; }
.user-card span { font-size: 10px; color: var(--accent); }
.btn-logout {
width: 100%;
background: transparent;
border: 1px solid var(--border);
border-radius: 8px;
padding: 9px;
color: var(--muted);
font-size: 12px;
cursor: pointer;
font-family: 'Noto Sans Myanmar', sans-serif;
transition: all 0.15s;
}
.btn-logout:hover { border-color: var(--red); color: var(--red); }
/* ── Main Content ── */
.main {
margin-left: 220px;
flex: 1;
padding: 24px;
min-height: 100vh;
}
/* ── Mobile Header ── */
.mob-header {
display: none;
background: var(--card);
border-bottom: 1px solid var(--border);
padding: 12px 16px;
align-items: center;
justify-content: space-between;
position: sticky; top: 0; z-index: 60;
}
.hamburger {
background: none; border: none;
color: var(--text); font-size: 20px; cursor: pointer;
}
.sidebar-overlay {
display: none;
position: fixed; inset: 0; background: rgba(0,0,0,0.6);
z-index: 49;
}
/* ── Page Header ── */
.page-header {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 24px;
}
.page-title { font-size: 20px; font-weight: 700; }
.page-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
/* ── Stat Cards ── */
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
margin-bottom: 24px;
}
.stat-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 16px;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 60px; height: 60px;
border-radius: 0 14px 0 60px;
opacity: 0.15;
}
.stat-card.gold::before { background: var(--accent); }
.stat-card.green::before { background: var(--green); }
.stat-card.blue::before { background: var(--blue); }
.stat-card.purple::before { background: var(--purple); }
.stat-icon { font-size: 22px; margin-bottom: 10px; }
.stat-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.stat-value {
font-size: 20px; font-weight: 700;
font-family: 'JetBrains Mono', monospace;
margin-top: 4px;
line-height: 1;
}
.stat-card.gold .stat-value { color: var(--accent); }
.stat-card.green .stat-value { color: var(--green); }
.stat-card.blue .stat-value { color: var(--blue); }
.stat-card.purple .stat-value { color: var(--purple); }
.stat-sub { font-size: 10px; color: var(--muted); margin-top: 4px; font-family: 'JetBrains Mono', monospace; }
/* ── Chart ── */
.chart-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 18px;
margin-bottom: 24px;
}
.chart-title { font-size: 13px; font-weight: 600; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.bar-chart { display: flex; align-items: flex-end; gap: 6px; height: 100px; }
.bar-wrap { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; height: 100%; justify-content: flex-end; }
.bar {
width: 100%;
background: linear-gradient(to top, var(--accent), rgba(245,158,11,0.4));
border-radius: 4px 4px 0 0;
min-height: 4px;
transition: height 0.6s ease;
position: relative;
}
.bar:hover { background: linear-gradient(to top, #fbbf24, var(--accent)); }
.bar-label { font-size: 9px; color: var(--muted); font-family: 'JetBrains Mono', monospace; white-space: nowrap; }
/* ── Tabs ── */
.tabs { display: flex; gap: 4px; margin-bottom: 16px; background: var(--card2); border-radius: 10px; padding: 4px; }
.tab {
flex: 1; text-align: center; padding: 8px 4px;
border-radius: 7px; font-size: 12px; cursor: pointer;
color: var(--muted); transition: all 0.2s;
}
.tab.active { background: var(--card); color: var(--text); font-weight: 600; }
/* ── Products Table ── */
.table-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 14px;
overflow: hidden;
margin-bottom: 24px;
}
.table-head {
padding: 14px 16px;
display: flex; align-items: center; justify-content: space-between;
border-bottom: 1px solid var(--border);
}
.table-head h3 { font-size: 13px; font-weight: 600; }
.btn-add {
background: var(--accent);
border: none; border-radius: 8px;
padding: 8px 14px; color: #000;
font-size: 12px; font-weight: 700;
cursor: pointer;
font-family: 'Noto Sans Myanmar', sans-serif;
display: flex; align-items: center; gap: 6px;
}
.search-bar {
padding: 10px 16px;
border-bottom: 1px solid var(--border);
}
.search-bar input {
width: 100%;
background: var(--card2);
border: 1px solid var(--border);
border-radius: 8px;
padding: 9px 12px;
color: var(--text);
font-size: 13px;
font-family: 'Noto Sans Myanmar', sans-serif;
outline: none;
}
.search-bar input:focus { border-color: var(--blue); }
.product-row {
display: flex; align-items: center; gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid rgba(30,41,59,0.5);
transition: background 0.1s;
}
.product-row:last-child { border: none; }
.product-row:hover { background: rgba(255,255,255,0.02); }
.prod-info { flex: 1; min-width: 0; }
.prod-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prod-barcode { font-size: 10px; color: var(--muted); font-family: 'JetBrains Mono', monospace; margin-top: 2px; }
.prod-price {
font-family: 'JetBrains Mono', monospace;
font-size: 13px; font-weight: 700;
color: var(--accent);
min-width: 80px; text-align: right;
}
.prod-stock { font-size: 11px; color: var(--muted); text-align: center; min-width: 40px; }
.btn-edit, .btn-del {
border: none; border-radius: 7px;
padding: 6px 10px; font-size: 12px;
cursor: pointer; transition: all 0.15s;
}
.btn-edit { background: rgba(59,130,246,0.15); color: var(--blue); }
.btn-edit:hover { background: rgba(59,130,246,0.25); }
.btn-del { background: rgba(239,68,68,0.15); color: var(--red); }
.btn-del:hover { background: rgba(239,68,68,0.25); }
/* ── Sales History ── */
.sale-row {
display: flex; align-items: center; gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid rgba(30,41,59,0.5);
cursor: pointer; transition: background 0.1s;
}
.sale-row:hover { background: rgba(255,255,255,0.02); }
.sale-row:last-child { border: none; }
.sale-id { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--muted); min-width: 40px; }
.sale-info { flex: 1; }
.sale-time { font-size: 11px; color: var(--muted); font-family: 'JetBrains Mono', monospace; }
.sale-items-count { font-size: 12px; color: var(--text); }
.sale-total { font-family: 'JetBrains Mono', monospace; font-size: 14px; font-weight: 700; color: var(--green); }
/* ── Top Products ── */
.top-prod-row {
display: flex; align-items: center; gap: 10px;
padding: 10px 0;
border-bottom: 1px solid rgba(30,41,59,0.5);
}
.top-prod-row:last-child { border: none; }
.top-rank { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--muted); min-width: 20px; }
.top-name { flex: 1; font-size: 12px; }
.top-qty { font-size: 11px; color: var(--muted); min-width: 40px; text-align: right; }
.top-rev { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--accent); min-width: 80px; text-align: right; }
/* ── Period Selector ── */
.period-tabs { display: flex; gap: 4px; margin-bottom: 16px; }
.period-tab {
padding: 7px 14px; border-radius: 20px;
font-size: 12px; cursor: pointer;
border: 1px solid var(--border);
color: var(--muted); background: none;
font-family: 'Noto Sans Myanmar', sans-serif;
transition: all 0.15s;
}
.period-tab.active { background: var(--accent); border-color: var(--accent); color: #000; font-weight: 700; }
/* ── Modal ── */
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.7);
z-index: 200; display: none;
align-items: center; justify-content: center;
padding: 16px;
}
.modal-overlay.show { display: flex; animation: fadeIn 0.2s; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.modal {
background: var(--card);
border: 1px solid var(--border);
border-radius: 18px;
padding: 24px;
width: 100%; max-width: 420px;
animation: scaleIn 0.25s ease;
max-height: 90vh; overflow-y: auto;
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
.modal h3 { font-size: 16px; margin-bottom: 20px; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 7px; }
.form-group input {
width: 100%; background: var(--card2);
border: 1px solid var(--border); border-radius: 10px;
padding: 12px 14px; color: var(--text);
font-size: 14px; font-family: 'Noto Sans Myanmar', sans-serif; outline: none;
}
.form-group input:focus { border-color: var(--accent); }
.form-group input[type="number"] { font-family: 'JetBrains Mono', monospace; }
.modal-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 20px; }
.btn-cancel {
background: var(--card2); border: 1px solid var(--border);
border-radius: 10px; padding: 12px; color: var(--text);
font-size: 14px; cursor: pointer;
font-family: 'Noto Sans Myanmar', sans-serif;
}
.btn-save {
background: var(--accent); border: none;
border-radius: 10px; padding: 12px; color: #000;
font-size: 14px; font-weight: 700; cursor: pointer;
font-family: 'Noto Sans Myanmar', sans-serif;
}
.error-msg {
background: rgba(239,68,68,0.1);
border: 1px solid rgba(239,68,68,0.3);
border-radius: 8px; padding: 10px 12px;
color: #fca5a5; font-size: 12px;
margin-top: 12px; display: none;
}
/* ── Sale Detail ── */
.sale-detail-item {
display: flex; justify-content: space-between;
padding: 8px 0; border-bottom: 1px solid rgba(30,41,59,0.5);
font-size: 13px;
}
.sale-detail-item:last-child { border: none; }
/* ── Empty State ── */
.empty { text-align: center; padding: 32px; color: var(--muted); font-size: 13px; }
.empty span { font-size: 32px; display: block; margin-bottom: 8px; }
/* ── Loading ── */
.loading { text-align: center; padding: 24px; color: var(--muted); font-size: 13px; }
/* ── Responsive ── */
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); }
.sidebar.open { transform: translateX(0); }
.sidebar-overlay.show { display: block; }
.main { margin-left: 0; padding: 16px; }
.mob-header { display: flex; }
.stats-grid { grid-template-columns: 1fr 1fr; }
}
</style>
</head>
<body>
<div class="layout">
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-logo">
<div class="logo-icon">πŸ›’</div>
<div>
<h1>ကုန်စုဢဆိုင် POS</h1>
<p>Admin Panel</p>
</div>
</div>
<nav class="nav">
<div class="nav-item active" data-page="dashboard" onclick="showPage('dashboard')">
<span class="nav-icon">πŸ“Š</span> Dashboard
</div>
<div class="nav-item" data-page="products" onclick="showPage('products')">
<span class="nav-icon">πŸ“¦</span> α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έα€™α€»α€¬α€Έ
</div>
<div class="nav-item" data-page="sales" onclick="showPage('sales')">
<span class="nav-icon">🧾</span> ရောင်းချမှတ်တမ်း
</div>
</nav>
<div class="sidebar-footer">
<div class="user-card">
<div class="user-avatar">πŸ‘‘</div>
<div>
<p id="adminName">Admin</p>
<span>Administrator</span>
</div>
</div>
<button class="btn-logout" onclick="logout()">πŸšͺ α€‘α€½α€€α€Ία€™α€Šα€Ί</button>
</div>
</div>
<div class="sidebar-overlay" id="overlay" onclick="closeSidebar()"></div>
<!-- Mobile Header -->
<div class="mob-header">
<button class="hamburger" onclick="openSidebar()">☰</button>
<span style="font-size:14px;font-weight:600" id="mobTitle">Dashboard</span>
<span style="font-size:20px">πŸ›’</span>
</div>
<!-- Main -->
<main class="main">
<!-- ── DASHBOARD PAGE ── -->
<div id="page-dashboard">
<div class="page-header">
<div>
<div class="page-title">Dashboard</div>
<div class="page-sub" id="dashDate"></div>
</div>
<button onclick="loadDashboard()" style="background:none;border:none;color:var(--muted);font-size:18px;cursor:pointer">↻</button>
</div>
<div class="stats-grid" id="statsGrid">
<div class="stat-card gold"><div class="stat-icon">πŸ’°</div><div class="stat-label">α€šα€”α€±α€· ရောင်းရငွေ</div><div class="stat-value" id="todayRev">-</div><div class="stat-sub" id="todayCnt">- α€€α€Όα€­α€™α€Ί</div></div>
<div class="stat-card green"><div class="stat-icon">πŸ“ˆ</div><div class="stat-label">α€€α€žα€α€Ία€•α€α€Ί ရောင်းရငွေ</div><div class="stat-value" id="weekRev">-</div><div class="stat-sub" id="weekCnt">- α€€α€Όα€­α€™α€Ί</div></div>
<div class="stat-card blue"><div class="stat-icon">πŸ“¦</div><div class="stat-label">α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έ α€™α€»α€­α€―α€Έ</div><div class="stat-value" id="prodCount">-</div><div class="stat-sub">products</div></div>
<div class="stat-card purple"><div class="stat-icon">⭐</div><div class="stat-label">Top Seller</div><div class="stat-value" style="font-size:12px;font-family:'Noto Sans Myanmar'" id="topSeller">-</div><div class="stat-sub" id="topSellerQty">-</div></div>
</div>
<div class="chart-card">
<div class="chart-title">πŸ“Š ၇ ရက်ထတွင်း ရောင်းချမှု</div>
<div class="bar-chart" id="barChart">
<div class="loading">Loading...</div>
</div>
</div>
<div class="table-card">
<div class="table-head"><h3>πŸ† α€€α€žα€α€Ία€•α€α€Ί ထရောင်းကောင်းဆုဢး</h3></div>
<div style="padding:0 16px" id="topProdsEl">
<div class="loading">Loading...</div>
</div>
</div>
</div>
<!-- ── PRODUCTS PAGE ── -->
<div id="page-products" style="display:none">
<div class="page-header">
<div>
<div class="page-title">α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έα€™α€»α€¬α€Έ</div>
<div class="page-sub" id="prodCountLabel">Loading...</div>
</div>
<button class="btn-add" onclick="openAddModal()">+ α€‘α€Šα€·α€Ία€™α€Šα€Ί</button>
</div>
<div class="table-card">
<div class="search-bar">
<input type="text" id="prodSearch" placeholder="πŸ” α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έα€”α€¬α€™α€Šα€Ί α€žα€­α€―α€·α€™α€Ÿα€―α€α€Ί Barcode ရှာပါ..." oninput="filterProducts()">
</div>
<div id="productsList"><div class="loading">Loading...</div></div>
</div>
</div>
<!-- ── SALES PAGE ── -->
<div id="page-sales" style="display:none">
<div class="page-header">
<div>
<div class="page-title">ရောင်းချမှတ်တမ်း</div>
</div>
</div>
<div class="period-tabs">
<button class="period-tab active" onclick="loadSales('today',this)">α€šα€”α€±α€·</button>
<button class="period-tab" onclick="loadSales('week',this)">α€€α€žα€α€Ία€•α€α€Ί</button>
<button class="period-tab" onclick="loadSales('month',this)">α€€α€œ</button>
<button class="period-tab" onclick="loadSales('all',this)">α€‘α€¬α€Έα€œα€―α€Άα€Έ</button>
</div>
<div class="stats-grid" style="grid-template-columns:1fr 1fr;margin-bottom:16px">
<div class="stat-card gold"><div class="stat-label">ရောင်းရငွေ</div><div class="stat-value" id="salesRevEl">0</div></div>
<div class="stat-card green"><div class="stat-label">ရောင်းချကြိမ်</div><div class="stat-value" id="salesCntEl">0</div></div>
</div>
<div class="table-card">
<div id="salesList"><div class="loading">Loading...</div></div>
</div>
</div>
</main>
</div>
<!-- Add/Edit Product Modal -->
<div class="modal-overlay" id="productModal">
<div class="modal">
<h3 id="modalTitle">α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έα€‘α€žα€…α€Ί α€‘α€Šα€·α€Ία€™α€Šα€Ί</h3>
<div class="form-group">
<label>Barcode</label>
<input type="text" id="fBarcode" placeholder="8850006111019" inputmode="numeric" style="font-family:'JetBrains Mono',monospace">
</div>
<div class="form-group">
<label>α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έα€”α€¬α€™α€Šα€Ί</label>
<input type="text" id="fName" placeholder="α€”α€¬α€™α€Šα€Ία€‘α€Šα€·α€Ία€•α€«...">
</div>
<div class="form-group">
<label>α€ˆα€±α€Έα€”α€Ύα€―α€”α€Ία€Έ (ကျပ်)</label>
<input type="number" id="fPrice" placeholder="0" min="0">
</div>
<div class="form-group">
<label>Stock (ခု)</label>
<input type="number" id="fStock" placeholder="0" min="0">
</div>
<div class="error-msg" id="modalErr"></div>
<div class="modal-btns">
<button class="btn-cancel" onclick="closeModal('productModal')">α€•α€šα€Ία€–α€»α€€α€Ί</button>
<button class="btn-save" onclick="saveProduct()">α€žα€­α€™α€Ία€Έα€™α€Šα€Ί</button>
</div>
</div>
</div>
<!-- Sale Detail Modal -->
<div class="modal-overlay" id="saleModal">
<div class="modal">
<h3>🧾 ရောင်းမှတ်တမ်း α€‘α€žα€±α€Έα€…α€­α€α€Ί</h3>
<div id="saleDetailContent"><div class="loading">Loading...</div></div>
<div style="margin-top:16px">
<button class="btn-cancel" style="width:100%" onclick="closeModal('saleModal')">α€•α€­α€α€Ία€™α€Šα€Ί</button>
</div>
</div>
</div>
<script>
let allProducts = [];
let editingProductId = null;
let currentPage = 'dashboard';
// Init
document.addEventListener('DOMContentLoaded', async () => {
const r = await fetch('/api/me');
if (!r.ok) { window.location.href = '/login'; return; }
const d = await r.json();
if (d.role !== 'admin') { window.location.href = '/cashier'; return; }
document.getElementById('adminName').textContent = d.username;
document.getElementById('dashDate').textContent = new Date().toLocaleDateString('my-MM');
loadDashboard();
});
// ── Navigation ─────────────────────────────────────────────────────────────
function showPage(page) {
currentPage = page;
['dashboard','products','sales'].forEach(p => {
document.getElementById(`page-${p}`).style.display = p === page ? 'block' : 'none';
document.querySelector(`[data-page="${p}"]`).classList.toggle('active', p === page);
});
const titles = { dashboard: 'Dashboard', products: 'α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έα€™α€»α€¬α€Έ', sales: 'ရောင်းချမှတ်တမ်း' };
document.getElementById('mobTitle').textContent = titles[page];
closeSidebar();
if (page === 'products') loadProducts();
if (page === 'sales') loadSales('today');
}
function openSidebar() {
document.getElementById('sidebar').classList.add('open');
document.getElementById('overlay').classList.add('show');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('overlay').classList.remove('show');
}
// ── Dashboard ──────────────────────────────────────────────────────────────
async function loadDashboard() {
try {
const r = await fetch('/api/dashboard');
const d = await r.json();
document.getElementById('todayRev').textContent = fmtK(d.today.rev);
document.getElementById('todayCnt').textContent = d.today.cnt + ' α€€α€Όα€­α€™α€Ί';
document.getElementById('weekRev').textContent = fmtK(d.week.rev);
document.getElementById('weekCnt').textContent = d.week.cnt + ' α€€α€Όα€­α€™α€Ί';
document.getElementById('prodCount').textContent = d.product_count;
if (d.top_products.length > 0) {
document.getElementById('topSeller').textContent = d.top_products[0].name.substring(0, 10);
document.getElementById('topSellerQty').textContent = d.top_products[0].qty + ' ခု';
}
// Bar chart
renderBarChart(d.daily_chart);
// Top products
const topEl = document.getElementById('topProdsEl');
if (d.top_products.length === 0) {
topEl.innerHTML = '<div class="empty"><span>πŸ“Š</span>ဒေတာ α€™α€›α€Ύα€­α€žα€±α€Έα€•α€«</div>';
} else {
topEl.innerHTML = d.top_products.map((p, i) => `
<div class="top-prod-row">
<span class="top-rank">#${i+1}</span>
<span class="top-name">${p.name}</span>
<span class="top-qty">${p.qty} ခု</span>
<span class="top-rev">${fmtK(p.revenue)}</span>
</div>
`).join('');
}
} catch(e) { console.error(e); }
}
function renderBarChart(data) {
const el = document.getElementById('barChart');
if (!data || data.length === 0) {
el.innerHTML = '<div class="loading" style="text-align:center;flex:1">ဒေတာ α€™α€›α€Ύα€­α€žα€±α€Έα€•α€«</div>';
return;
}
const max = Math.max(...data.map(d => d.rev), 1);
el.innerHTML = data.map(d => {
const h = Math.max(4, Math.round((d.rev / max) * 90));
const label = d.day.substring(5); // MM-DD
return `
<div class="bar-wrap">
<div class="bar" style="height:${h}px" title="${d.day}: ${d.rev.toLocaleString()} ကျပ်"></div>
<div class="bar-label">${label}</div>
</div>
`;
}).join('');
}
// ── Products ───────────────────────────────────────────────────────────────
async function loadProducts() {
const el = document.getElementById('productsList');
el.innerHTML = '<div class="loading">Loading...</div>';
try {
const r = await fetch('/api/products');
allProducts = await r.json();
document.getElementById('prodCountLabel').textContent = `စုစုပေါင်း ${allProducts.length} α€™α€»α€­α€―α€Έ`;
renderProducts(allProducts);
} catch(e) { el.innerHTML = '<div class="empty">ချိတ်ဆက်မှု ပြဿနာ</div>'; }
}
function filterProducts() {
const q = document.getElementById('prodSearch').value.toLowerCase();
const filtered = allProducts.filter(p =>
p.name.toLowerCase().includes(q) || p.barcode.includes(q)
);
renderProducts(filtered);
}
function renderProducts(list) {
const el = document.getElementById('productsList');
if (list.length === 0) {
el.innerHTML = '<div class="empty"><span>πŸ“¦</span>α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έ မတွေ့ပါ</div>';
return;
}
el.innerHTML = list.map(p => `
<div class="product-row">
<div class="prod-info">
<div class="prod-name">${p.name}</div>
<div class="prod-barcode">${p.barcode}</div>
</div>
<div class="prod-stock">${p.stock}<br><span style="font-size:9px">ခု</span></div>
<div class="prod-price">${p.price.toLocaleString()}<span style="font-size:10px;color:var(--muted)"> ကျပ်</span></div>
<button class="btn-edit" onclick="openEditModal(${p.id})">✏️</button>
<button class="btn-del" onclick="deleteProduct(${p.id},'${p.name.replace(/'/g,"\\'")}')">πŸ—‘οΈ</button>
</div>
`).join('');
}
function openAddModal() {
editingProductId = null;
document.getElementById('modalTitle').textContent = 'α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έα€‘α€žα€…α€Ί α€‘α€Šα€·α€Ία€™α€Šα€Ί';
document.getElementById('fBarcode').value = '';
document.getElementById('fName').value = '';
document.getElementById('fPrice').value = '';
document.getElementById('fStock').value = '';
document.getElementById('modalErr').style.display = 'none';
document.getElementById('productModal').classList.add('show');
setTimeout(() => document.getElementById('fBarcode').focus(), 100);
}
function openEditModal(id) {
const p = allProducts.find(x => x.id === id);
if (!p) return;
editingProductId = id;
document.getElementById('modalTitle').textContent = 'α€€α€―α€”α€Ία€•α€…α€Ήα€…α€Šα€Ία€Έ α€•α€Όα€„α€Ία€™α€Šα€Ί';
document.getElementById('fBarcode').value = p.barcode;
document.getElementById('fName').value = p.name;
document.getElementById('fPrice').value = p.price;
document.getElementById('fStock').value = p.stock;
document.getElementById('modalErr').style.display = 'none';
document.getElementById('productModal').classList.add('show');
}
async function saveProduct() {
const barcode = document.getElementById('fBarcode').value.trim();
const name = document.getElementById('fName').value.trim();
const price = document.getElementById('fPrice').value;
const stock = document.getElementById('fStock').value;
const errEl = document.getElementById('modalErr');
if (!barcode || !name || !price) {
errEl.textContent = 'Barcode, α€”α€¬α€™α€Šα€Ία€”α€Ύα€„α€·α€Ί α€ˆα€±α€Έα€”α€Ύα€―α€”α€Ία€Έ α€‘α€Šα€·α€Ία€•α€«';
errEl.style.display = 'block'; return;
}
const url = editingProductId ? `/api/products/${editingProductId}` : '/api/products';
const method = editingProductId ? 'PUT' : 'POST';
try {
const r = await fetch(url, {
method, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ barcode, name, price, stock: stock || 0 })
});
const d = await r.json();
if (r.ok) {
closeModal('productModal');
loadProducts();
} else {
errEl.textContent = d.error || 'Error α€–α€Όα€…α€Ία€žα€Šα€Ί';
errEl.style.display = 'block';
}
} catch(e) {
errEl.textContent = 'ချိတ်ဆက်မှု ပြဿနာ';
errEl.style.display = 'block';
}
}
async function deleteProduct(id, name) {
if (!confirm(`"${name}" α€€α€­α€― ဖျက်မှာ α€žα€±α€α€»α€¬α€•α€«α€žα€œα€¬α€Έ?`)) return;
const r = await fetch(`/api/products/${id}`, { method: 'DELETE' });
if (r.ok) loadProducts();
else alert('ဖျက်မရပါ');
}
// ── Sales ──────────────────────────────────────────────────────────────────
async function loadSales(period, btn) {
if (btn) {
document.querySelectorAll('.period-tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
}
const el = document.getElementById('salesList');
el.innerHTML = '<div class="loading">Loading...</div>';
try {
const r = await fetch(`/api/sales?period=${period}`);
const d = await r.json();
document.getElementById('salesRevEl').textContent = fmtK(d.summary.total_revenue);
document.getElementById('salesCntEl').textContent = d.summary.total_sales;
if (d.sales.length === 0) {
el.innerHTML = '<div class="empty"><span>🧾</span>မှတ်တမ်း α€™α€›α€Ύα€­α€žα€±α€Έα€•α€«</div>';
return;
}
el.innerHTML = d.sales.map(s => `
<div class="sale-row" onclick="showSaleDetail(${s.id})">
<div class="sale-id">#${s.id}</div>
<div class="sale-info">
<div class="sale-time">${s.created_at}</div>
<div class="sale-items-count">${s.item_count} α€™α€»α€­α€―α€Έ β€’ ${s.cashier_name || 'cashier'}</div>
</div>
<div class="sale-total">${s.total_amount.toLocaleString()} ကျပ်</div>
</div>
`).join('');
} catch(e) { el.innerHTML = '<div class="empty">ချိတ်ဆက်မှု ပြဿနာ</div>'; }
}
async function showSaleDetail(id) {
document.getElementById('saleModal').classList.add('show');
const el = document.getElementById('saleDetailContent');
el.innerHTML = '<div class="loading">Loading...</div>';
try {
const r = await fetch(`/api/sales/${id}/items`);
const items = await r.json();
const total = items.reduce((s, i) => s + i.price_at_time * i.quantity, 0);
el.innerHTML = `
<div style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--muted);margin-bottom:12px">Sale #${id}</div>
${items.map(i => `
<div class="sale-detail-item">
<span>${i.name} <span style="color:var(--muted)">Γ—${i.quantity}</span></span>
<span style="color:var(--accent)">${(i.price_at_time * i.quantity).toLocaleString()} ကျပ်</span>
</div>
`).join('')}
<div class="sale-detail-item" style="font-weight:700;margin-top:8px;border-top:1px solid var(--border);padding-top:10px">
<span style="color:var(--accent)">စုစုပေါင်း</span>
<span style="color:var(--accent);font-family:'JetBrains Mono',monospace">${total.toLocaleString()} ကျပ်</span>
</div>
`;
} catch(e) { el.innerHTML = '<div class="empty">Error</div>'; }
}
// ── Helpers ────────────────────────────────────────────────────────────────
function fmtK(n) {
if (n >= 1000000) return (n/1000000).toFixed(1) + 'M';
if (n >= 1000) return (n/1000).toFixed(0) + 'K';
return Math.round(n).toLocaleString();
}
function closeModal(id) {
document.getElementById(id).classList.remove('show');
}
function logout() {
fetch('/api/logout', { method: 'POST' })
.then(() => window.location.href = '/login');
}
// Close modal on overlay click
document.querySelectorAll('.modal-overlay').forEach(el => {
el.addEventListener('click', function(e) {
if (e.target === this) this.classList.remove('show');
});
});
</script>
</body>
</html>