diff --git a/public/app.js b/public/app.js index e663e9a..0af1b26 100644 --- a/public/app.js +++ b/public/app.js @@ -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(); } diff --git a/public/extras.css b/public/extras.css index 40b2bfb..85c9e8a 100644 --- a/public/extras.css +++ b/public/extras.css @@ -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}} diff --git a/public/index.html b/public/index.html index b42e5ba..1a5dd16 100644 --- a/public/index.html +++ b/public/index.html @@ -1,5 +1,5 @@ vSeeBox Stockroom
SStockroom

Inventory Overview

Available products0Ready to sell
Sold this month0No sales recorded
Inventory value$0Based on purchase cost
-
+