Add meaningful database connection status
This commit is contained in:
+8
-5
@@ -8,13 +8,16 @@ const fmtDateTime = (v) => v ? new Date(`${v.replace(" ","T")}Z`).toLocaleString
|
||||
const esc = (v = "") => String(v).replace(/[&<>'"]/g, c => ({ "&":"&", "<":"<", ">":">", "'":"'", '"':""" })[c]);
|
||||
const ids = p => [["UID",p.uid],["SN",p.sn],["MAC",p.mac]].filter(([,v]) => v);
|
||||
const primaryId = p => ids(p)[0]?.[1] || "No identifier";
|
||||
function storageStatus(state,message) { const el=$("#storageStatus"); if(!el)return; el.className=`storage-status ${state}`; el.querySelector("b").textContent=message; }
|
||||
|
||||
async function api(path, options = {}) {
|
||||
const response = await fetch(path, { headers: { "content-type":"application/json" }, ...options });
|
||||
if (response.status === 204) return null;
|
||||
const data = await response.json();
|
||||
if (!response.ok) throw new Error(data.error || "Something went wrong.");
|
||||
return data;
|
||||
const writing=options.method&&options.method!=="GET"; if(writing)storageStatus("saving","Saving to database");
|
||||
try { const response = await fetch(path, { headers: { "content-type":"application/json" }, ...options });
|
||||
if (response.status === 204) { storageStatus("connected","Database connected"); return null; }
|
||||
const data = await response.json();
|
||||
if (!response.ok) { storageStatus("connected","Database connected"); const error=new Error(data.error||"Something went wrong."); error.databaseHealthy=true; throw error; }
|
||||
storageStatus("connected","Database connected"); return data;
|
||||
} catch(error) { if(!error.databaseHealthy)storageStatus("critical","Database error"); throw error; }
|
||||
}
|
||||
function toast(message) { const el=$("#toast"); el.textContent=message; el.hidden=false; clearTimeout(toast.timer); toast.timer=setTimeout(()=>el.hidden=true,2600); }
|
||||
function openModal(html) { $("#modalBody").innerHTML=html; $("#modal").showModal(); }
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
#pagination{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;color:#68758b;font-size:13px;border-top:1px solid #edf0f5}#pagination div{display:flex;gap:8px}#pagination button{background:#fff;border:1px solid #d7deea;padding:7px 11px}#pagination button:disabled{opacity:.4;cursor:default}
|
||||
.address-grid{display:grid;grid-template-columns:2fr 1fr 1fr;gap:10px}.detail-card{background:#f7f9fc;border:1px solid #e2e7ef;border-radius:10px;padding:14px;margin:14px 0}.detail-card h3,.customer-purchases h3{margin:0 0 10px}.detail-card p{margin:4px 0!important}.customer-purchases{max-height:330px;overflow:auto}.customer-purchases article{border-top:1px solid #e5e9f0;padding:12px 0}.customer-purchases article:first-child{border-top:0}.customer-purchases button{padding:6px 9px;background:#edf3ff;color:#1d4ed8}@media(max-width:760px){.address-grid{grid-template-columns:1fr}#pagination{align-items:flex-start;flex-direction:column}#pagination>div{width:100%;align-items:center;justify-content:space-between}}
|
||||
.customer-notes{margin:18px 0}.customer-notes h3{margin-bottom:8px}.customer-notes article{position:relative;border:1px solid #e2e7ef;border-radius:9px;padding:12px;margin:8px 0}.customer-notes article>div{display:flex;align-items:center;gap:8px;color:#667085}.customer-notes article p{white-space:pre-wrap;margin:10px 0}.customer-notes article>button{position:absolute;right:8px;top:8px;padding:5px 8px;background:#fff0f0;color:#c62828}.note-category{background:#edf3ff;color:#1d4ed8;border-radius:99px;padding:3px 8px;font-size:12px;font-weight:700}
|
||||
.storage-status{margin-left:auto!important;display:flex;align-items:center;gap:7px;white-space:nowrap}.storage-status i{width:9px;height:9px;border-radius:50%;background:#f59e0b;box-shadow:0 0 0 3px #f59e0b22}.storage-status b{font-weight:600}.storage-status.connected i{background:#16a34a;box-shadow:0 0 0 3px #16a34a22}.storage-status.saving i,.storage-status.connecting i{background:#f59e0b;box-shadow:0 0 0 3px #f59e0b22;animation:status-pulse 1s infinite}.storage-status.critical{color:#b42318!important}.storage-status.critical i{background:#dc2626;box-shadow:0 0 0 3px #dc262622}@keyframes status-pulse{50%{opacity:.4}}@media(max-width:760px){.storage-status{display:flex!important;flex-basis:100%;margin-left:0!important;padding-bottom:10px}.records nav{height:auto;flex-wrap:wrap}}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>vSeeBox Stockroom</title><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/extras.css"></head>
|
||||
<body><header><div class="brand"><b>S</b><span>Stockroom</span></div><input id="search" type="search" placeholder="Search UID, SN, MAC, model, customer…"><div class="actions"><button class="secondary" data-open="receive">+ Receive product</button><button class="primary" data-open="sell">Record a sale</button></div></header>
|
||||
<main><div class="intro"><h1>Inventory Overview</h1><span id="date"></span></div><section class="stats"><article><span>Available products</span><strong id="availableCount">0</strong><small>Ready to sell</small></article><article><span>Sold this month</span><strong id="soldCount">0</strong><small id="revenue">No sales recorded</small></article><article><span>Inventory value</span><strong id="value">$0</strong><small>Based on purchase cost</small></article></section>
|
||||
<section class="records"><nav><button class="tab active" data-tab="available">Available inventory <i id="availableBadge">0</i></button><button class="tab" data-tab="sold">Sales history <i id="soldBadge">0</i></button><button class="tab" data-tab="customers">Customers <i id="customerBadge">0</i></button><span>● Saved in your local database</span></nav><div class="table-wrap"><table><thead id="thead"></thead><tbody id="rows"></tbody></table><div id="empty" hidden></div></div><footer id="pagination"></footer></section></main>
|
||||
<section class="records"><nav><button class="tab active" data-tab="available">Available inventory <i id="availableBadge">0</i></button><button class="tab" data-tab="sold">Sales history <i id="soldBadge">0</i></button><button class="tab" data-tab="customers">Customers <i id="customerBadge">0</i></button><span id="storageStatus" class="storage-status connecting" role="status" aria-live="polite"><i></i><b>Connecting to database</b></span></nav><div class="table-wrap"><table><thead id="thead"></thead><tbody id="rows"></tbody></table><div id="empty" hidden></div></div><footer id="pagination"></footer></section></main>
|
||||
<dialog id="modal"><button class="close" aria-label="Close">×</button><div id="modalBody"></div></dialog><div id="toast" hidden></div><script type="module" src="/app.js"></script></body></html>
|
||||
|
||||
Reference in New Issue
Block a user