<!-- ===================== Dashboard UI (PerfectPanel Admin API via CF Worker) ===================== -->
<style>
:root { --bg:#0b1220; --card:#121a2b; --muted:#9aa4b2; --accent:#22c55e; --warn:#f59e0b; --err:#ef4444; --txt:#e5e7eb; --line:#1e293b; }
.pp-wrap{font-family:Inter,system-ui,Segoe UI,Arial,sans-serif;color:var(--txt)}
.pp-grid{display:grid;gap:16px}
.pp-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.pp-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.pp-4{grid-template-columns:repeat(4,minmax(0,1fr))}
.pp-card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px}
.pp-heading{display:flex;align-items:center;justify-content:space-between;margin:6px 0 12px 0}
.pp-title{font-weight:700;font-size:16px}
.pp-sub{font-size:12px;color:var(--muted)}
.pp-kpi{font-size:28px;font-weight:800}
.pp-row{display:flex;gap:10px;align-items:center}
.pp-badge{border:1px solid var(--line);border-radius:20px;padding:4px 8px;font-size:11px;color:var(--muted)}
.pp-table{width:100%;border-collapse:collapse;font-size:13px}
.pp-table th,.pp-table td{padding:10px;border-bottom:1px solid var(--line);text-align:left}
.pp-chip{display:inline-block;padding:4px 8px;border-radius:999px;font-size:11px}
.pp-chip.ok{background:#052e1a;color:#6ee7b7}
.pp-chip.warn{background:#2a1f09;color:#f9d76c}
.pp-chip.err{background:#2a0f12;color:#fca5a5}
.pp-chip.neutral{background:#111827;color:#9aa4b2}
.pp-right{margin-left:auto}
.pp-topbar{display:flex;align-items:center;gap:8px;margin-bottom:12px}
.pp-select{background:var(--card);color:var(--txt);border:1px solid var(--line);border-radius:8px;padding:8px}
.pp-btn{background:#1f2937;border:1px solid var(--line);color:var(--txt);padding:8px 10px;border-radius:8px;cursor:pointer}
.pp-btn:hover{opacity:.9}
.pp-notice{position:fixed;right:16px;top:16px;display:flex;flex-direction:column;gap:8px;z-index:99999}
.pp-toast{background:#0b1220;border:1px solid var(--line);border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.35);padding:12px 14px;min-width:280px}
.pp-toast .hdr{display:flex;align-items:center;justify-content:space-between;font-weight:700;margin-bottom:6px}
.pp-toast .txt{font-size:13px;color:var(--muted)}
.pp-nbar{position:fixed;right:16px;top:92px;width:320px;max-height:60vh;overflow:auto;border:1px solid var(--line);background:var(--card);border-radius:12px;padding:10px;display:none;z-index:99990}
.pp-nbar h4{margin:6px 0 10px 0}
.pp-nitem{border-bottom:1px solid var(--line);padding:8px 4px}
.pp-nitem:last-child{border-bottom:none}
@media (max-width:1024px){.pp-4{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.pp-2,.pp-3,.pp-4{grid-template-columns:1fr}}
</style>
<div class="pp-wrap">
<!-- Top controls -->
<div class="pp-topbar">
<div class="pp-title">Dashboard</div>
<div class="pp-right pp-row">
<!-- Notification center -->
<button id="pp-open-nbar" class="pp-btn" title="Notifications">Notifications</button>
<!-- Language switcher -->
<select id="pp-lang" class="pp-select" title="Language">
<option value="">Language</option>
<option value="en">English (Default)</option>
<option value="ar">Arabic</option>
<option value="bn">Bengali</option>
<option value="de">German</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="hi">Hindi</option>
<option value="id">Indonesian</option>
<option value="it">Italian</option>
<option value="ja">Japanese</option>
<option value="ko">Korean</option>
<option value="ms">Malay</option>
<option value="nl">Dutch</option>
<option value="pt">Portuguese</option>
<option value="ru">Russian</option>
<option value="th">Thai</option>
<option value="tr">Turkish</option>
<option value="uk">Ukrainian</option>
<option value="ur">Urdu</option>
<option value="vi">Vietnamese</option>
</select>
<button id="pp-reset-en" class="pp-btn">Restore English</button>
</div>
</div>
<!-- KPI cards -->
<div class="pp-grid pp-4">
<div class="pp-card">
<div class="pp-heading"><div class="pp-title">Orders Today</div><div class="pp-badge" id="kpi-orders-badge">loading…</div></div>
<div class="pp-kpi" id="kpi-orders">–</div>
<div class="pp-sub">New orders created since 00:00 (panel time)</div>
</div>
<div class="pp-card">
<div class="pp-heading"><div class="pp-title">Payments Today</div><div class="pp-badge" id="kpi-payments-badge">loading…</div></div>
<div class="pp-kpi" id="kpi-payments">–</div>
<div class="pp-sub">Count of payments created today</div>
</div>
<div class="pp-card">
<div class="pp-heading"><div class="pp-title">New Users Today</div><div class="pp-badge" id="kpi-users-badge">loading…</div></div>
<div class="pp-kpi" id="kpi-users">–</div>
<div class="pp-sub">Registered users since midnight</div>
</div>
<div class="pp-card">
<div class="pp-heading"><div class="pp-title">Open Tickets Today</div><div class="pp-badge" id="kpi-tickets-badge">loading…</div></div>
<div class="pp-kpi" id="kpi-tickets">–</div>
<div class="pp-sub">Tickets created today (all statuses)</div>
</div>
</div>
<!-- Latest lists -->
<div class="pp-grid pp-2" style="margin-top:16px">
<div class="pp-card">
<div class="pp-heading"><div class="pp-title">Latest Orders</div><button id="reload-orders" class="pp-btn">Reload</button></div>
<table class="pp-table" id="tbl-orders">
<thead><tr><th>ID</th><th>User</th><th>Service</th><th>Status</th><th>Created</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div class="pp-card">
<div class="pp-heading"><div class="pp-title">Latest Payments</div><button id="reload-payments" class="pp-btn">Reload</button></div>
<table class="pp-table" id="tbl-payments">
<thead><tr><th>ID</th><th>User</th><th>Method</th><th>Amount</th><th>Status</th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<!-- Toast container + Notification bar -->
<div class="pp-notice" id="pp-notice"></div>
<div class="pp-nbar" id="pp-nbar">
<h4>Notifications</h4>
<div id="pp-nlist"></div>
<div style="margin-top:8px;display:flex;gap:8px">
<button id="pp-clear-nbar" class="pp-btn">Clear</button>
<button id="pp-close-nbar" class="pp-btn">Close</button>
</div>
</div>
<!-- Google Translate (hidden) -->
<div id="google_translate_element" style="height:0;overflow:hidden"></div>
<script>
(function addGTranslate(){
const s=document.createElement('script');
s.type='text/javascript';
s.src='//translate.google.com/translate_a/element.js?cb=__gtInit';
document.head.appendChild(s);
window.__gtInit=function(){
new google.translate.TranslateElement({
pageLanguage:'en',
includedLanguages:'en,ar,bn,de,es,fr,hi,id,it,ja,ko,ms,nl,pt,ru,th,tr,uk,ur,vi',
autoDisplay:false
},'google_translate_element');
}
})();
</script>
<script>
/* ====================== CONFIG ====================== */
const API_BASE = 'https://panelapi-proxy.panelempire.workers.dev/api'; // your Worker
const PAGE_LIMIT = 50; // list size for latest tables
/* ====================== UTIL ====================== */
const $ = (sel, el=document)=>el.querySelector(sel);
const $$ = (sel, el=document)=>el.querySelectorAll(sel);
const fmt = (d)=>new Date(d.replace(' ', 'T')+'Z').toLocaleString();
/* Toasts + Notification center */
const Notice = (() => {
const box = $('#pp-notice');
const bar = $('#pp-nbar');
const list = $('#pp-nlist');
const KEY='pp_nbar_items';
let items = JSON.parse(localStorage.getItem(KEY)||'[]');
function renderBar(){
list.innerHTML = items.map(i => `
<div class="pp-nitem">
<div style="font-weight:600">${i.title}</div>
<div class="pp-sub">${i.text}</div>
</div>`).join('') || '<div class="pp-sub">No notifications</div>';
}
function addToBar(title, text){
items.unshift({title,text,ts:Date.now()});
items = items.slice(0,50);
localStorage.setItem(KEY, JSON.stringify(items));
renderBar();
}
function toast(title, text, ttl=3500){
const t = document.createElement('div');
t.className='pp-toast';
t.innerHTML = `<div class="hdr"><div>${title}</div><button class="pp-btn" style="padding:2px 8px">×</button></div>
<div class="txt">${text}</div>`;
box.appendChild(t);
addToBar(title, text);
const closer = t.querySelector('button');
const close = ()=>t.remove();
closer.onclick = close;
setTimeout(close, ttl);
}
renderBar();
return { toast, open(){bar.style.display='block'}, close(){bar.style.display='none'}, clear(){items=[];localStorage.setItem(KEY,'[]');renderBar()} };
})();
/* Language switch (Google Translate) */
(function(){
const sel = $('#pp-lang');
const reset = $('#pp-reset-en');
sel.addEventListener('change', ()=>{
const lang = sel.value;
// mimic clicks on Google widget
const combo = document.querySelector('.goog-te-combo');
if (combo) { combo.value = lang || 'en'; combo.dispatchEvent(new Event('change')); }
Notice.toast('Language', lang ? 'Language changed.' : 'Language unchanged.');
});
reset.addEventListener('click', ()=>{
const combo = document.querySelector('.goog-te-combo');
if (combo) { combo.value = 'en'; combo.dispatchEvent(new Event('change')); }
sel.value = '';
Notice.toast('Language', 'Restored to English.');
});
})();
/* ====================== API WRAPPER ====================== */
async function call(endpoint, params={}){
const url = new URL(API_BASE + endpoint);
Object.entries(params).forEach(([k,v]) => url.searchParams.set(k, v));
const res = await fetch(url, { credentials:'omit' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
/* ====================== DASHBOARD ====================== */
function statusChip(s){
s = (s||'').toLowerCase();
const map = { completed:'ok', processing:'ok', in_progress:'ok', pending:'neutral', canceled:'err', partial:'warn', error:'err', fail:'err' };
const cls = map[s] || 'neutral';
const lbl = s.replace('_',' ') || 'unknown';
return `<span class="pp-chip ${cls}">${lbl}</span>`;
}
async function loadKPIs(){
const start = new Date(); start.setHours(0,0,0,0);
const from = Math.floor(start.getTime()/1000);
// we only need counts; limit high enough for typical daily volume
const [orders, payments, users, tickets] = await Promise.all([
call('/orders', { created_from: from, limit: 1000, sort: 'date-desc' }),
call('/payments', { created_from: from, limit: 1000, sort: 'created-at-desc' }),
call('/users', { created_from: from, limit: 1000, sort: 'created-at-desc' }),
call('/tickets', { created_from: from, limit: 1000, sort: 'created-at-desc' }),
]);
const k = (sel, val, badge='Ready') => { $(sel).textContent = val; $(sel+'-badge')?.textContent = badge; };
k('#kpi-orders', orders?.data?.list?.length ?? 0);
k('#kpi-payments', payments?.data?.list?.length ?? 0);
k('#kpi-users', users?.data?.list?.length ?? 0);
k('#kpi-tickets', tickets?.data?.list?.length ?? 0);
Notice.toast('Welcome!', 'Your dashboard is ready.');
}
async function loadOrders(){
const data = await call('/orders', { limit: PAGE_LIMIT, sort: 'date-desc' });
const rows = (data?.data?.list ?? []).map(o => `
<tr>
<td>${o.id}</td>
<td>${o.user||''}</td>
<td>${o.service_name||''}</td>
<td>${statusChip(o.status)}</td>
<td>${o.created ? fmt(o.created) : ''}</td>
</tr>
`).join('');
$('#tbl-orders tbody').innerHTML = rows || `<tr><td colspan="5" class="pp-sub">No orders found.</td></tr>`;
}
async function loadPayments(){
const data = await call('/payments', { limit: PAGE_LIMIT, sort: 'created-at-desc' });
const rows = (data?.data?.list ?? []).map(p => `
<tr>
<td>${p.payment_id}</td>
<td>${p.user?.username || ''}</td>
<td>${p.method||''}</td>
<td>${p.amount?.formatted || ''}</td>
<td>${statusChip(p.status)}</td>
</tr>
`).join('');
$('#tbl-payments tbody').innerHTML = rows || `<tr><td colspan="5" class="pp-sub">No payments found.</td></tr>`;
}
/* ====================== INIT ====================== */
(async () => {
try{
// One-time “loading” toast
Notice.toast('Loading', 'Fetching data…', 1800);
await Promise.all([loadKPIs(), loadOrders(), loadPayments()]);
}catch(e){
console.error(e);
Notice.toast('Error', 'Failed to load dashboard.');
}
// Controls
$('#reload-orders').onclick = loadOrders;
$('#reload-payments').onclick = loadPayments;
$('#pp-open-nbar').onclick = ()=>Notice.open();
$('#pp-close-nbar').onclick = ()=>Notice.close();
$('#pp-clear-nbar').onclick = ()=>Notice.clear();
})();
</script>
<!-- ===================== /end Dashboard UI ===================== -->