From e9879a7528639165d6743ced0129549296020360 Mon Sep 17 00:00:00 2001
From: mfwadejr <125498560+mfwadejr@users.noreply.github.com>
Date: Sun, 30 Aug 2026 15:47:52 -0400
Subject: [PATCH] Add adaptive fulfillment and warranty interface
---
public/app.js | 32 ++++++++++++++++++++++++--------
public/extras.css | 1 +
public/theme.css | 6 ++++--
3 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/public/app.js b/public/app.js
index 63b2d3c..cbecfcd 100644
--- a/public/app.js
+++ b/public/app.js
@@ -1,5 +1,5 @@
const $ = (s) => document.querySelector(s);
-const state = { products: [], models: [], tab: "available", query: "", page: 1, user: null };
+const state = { products: [], models: [], warranties: [], tab: "available", query: "", page: 1, user: null };
const PAGE_SIZE = 10;
const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
const today = () => new Date().toISOString().slice(0, 10);
@@ -27,6 +27,13 @@ function toast(message) { const el=$("#toast"); el.textContent=message; el.hidde
function openModal(html) { $("#modalBody").innerHTML=html; $("#modal").showModal(); }
function closeModal() { $("#modal").close(); }
function address(p) { return [p.shipAddress1,p.shipAddress2,[p.shipCity,p.shipState].filter(Boolean).join(", "),p.shipZip].filter(Boolean).join(" · "); }
+function warrantyState(p){if(!p.warrantyName)return{label:"Not recorded",className:"neutral"};if(!p.warrantyEndDate)return{label:p.warrantyName==="No Warranty"?"No Warranty":p.warrantyName,className:"neutral"};const days=Math.round((new Date(`${p.warrantyEndDate}T12:00:00Z`)-new Date(`${today()}T12:00:00Z`))/86400000);return days>=0?{label:`In Warranty · ${days} day${days===1?"":"s"} remaining`,className:"in-warranty"}:{label:`Expired · ${Math.abs(days)} day${days===-1?"":"s"} ago`,className:"expired"};}
+function warrantyBadge(p){const w=warrantyState(p);return `${esc(w.label)} `;}
+function trackingUrl(p){if(!p.trackingNumber)return"";const n=encodeURIComponent(p.trackingNumber);return p.carrier==="UPS"?`https://www.ups.com/track?tracknum=${n}`:p.carrier==="FedEx"?`https://www.fedex.com/fedextrack/?trknbr=${n}`:p.carrier==="USPS"?`https://tools.usps.com/go/TrackConfirmAction?tLabels=${n}`:"";}
+const fulfillmentTitle=method=>({Shipped:"Shipped to","Dropped Off":"Dropped off","Installed At":"Installed at",Meet:"Meet"}[method]||"Fulfillment");
+function warrantyOptions(selected=""){return state.warranties.map(w=>`${esc(w.name)} `).join("");}
+function fulfillmentFields(method="",p={}){if(!method)return'
Select a delivery method to enter its details.
';const addressRequired=new Set(["Shipped","Installed At"]).has(method),casual=new Set(["Dropped Off","Meet"]).has(method),nameLabel=method==="Meet"?"Meeting place / venue":method==="Dropped Off"?"Drop-off location / venue":"Location name (optional)",noteLabel=method==="Meet"?"Meet details":method==="Dropped Off"?"Drop-off details":"Fulfillment notes";return `${fulfillmentTitle(method)} ${method!=="Shipped"?`
${nameLabel} `:""}
Street address${addressRequired?"":" (optional)"} Apartment, suite, or unit City State ZIP code
${method==="Shipped"?`
CarrierChoose carrier ${["UPS","FedEx","USPS","Other"].map(x=>`${x} `).join("")} Tracking number (optional)
Delivery status${["Awaiting Tracking","Label Created","In Transit","Out for Delivery","Delivered","Exception","Returned"].map(x=>`${x} `).join("")} `:""}
${noteLabel} ${casual?'
Enter at least a venue, address, or detail. ':""}
`;}
+function bindFulfillment(form,p={}){const select=form.querySelector('[name="fulfillmentMethod"]'),target=form.querySelector(".fulfillment-fields");const draw=()=>target.innerHTML=fulfillmentFields(select.value,p);select.onchange=()=>{p={};draw();};draw();}
function idCell(p) { return ids(p).length ? ids(p).map(([k,v])=>`${k}: ${esc(v)}`).join("") : "Not entered "; }
function customers() {
const grouped=new Map();
@@ -54,14 +61,14 @@ function render() {
$(".table-wrap").hidden=false; $("#adminPanel").hidden=true;
const all=filtered(), pages=Math.max(1,Math.ceil(all.length/PAGE_SIZE)); state.page=Math.min(state.page,pages);
const start=(state.page-1)*PAGE_SIZE, rows=all.slice(start,start+PAGE_SIZE);
- $("#thead").innerHTML=state.tab==="available"?"Product UID / SN / MAC Received Cost Status ":state.tab==="customers"?"Customer Phone Purchases Last purchase Total spent ":"Product Customer UID / SN / MAC Sold Payment Sale price ";
+ $("#thead").innerHTML=state.tab==="available"?"Product UID / SN / MAC Received Cost Status ":state.tab==="customers"?"Customer Phone Purchases Last purchase Total spent ":"Product Customer Sold Payment Sale price Warranty ";
$("#rows").innerHTML=rows.map(p=>state.tab==="available"?inventoryRow(p):state.tab==="customers"?customerRow(p):saleRow(p)).join("");
decorateResponsiveTable();
$("#empty").hidden=Boolean(rows.length); $("#empty").textContent=state.query?"No records match your search.":state.tab==="available"?"No products are available.":state.tab==="customers"?"Customer records will appear after the first sale.":"No sales have been recorded.";
$("#pagination").innerHTML=all.length?`Showing ${start+1}–${Math.min(start+PAGE_SIZE,all.length)} of ${all.length} Previous Page ${state.page} of ${pages} Next
`:"";
}
function inventoryRow(p) { return `${esc(p.model)} ${esc(p.manufacturer)} · ${esc(p.condition)} ${idCell(p)}
${fmtDate(p.receivedAt)} ${p.cost?money.format(p.cost):"—"} Available ${state.user.role==="admin"?`Sell Delete `:""}
`; }
-function saleRow(p) { return `${esc(p.model)} ${esc(p.manufacturer)} · ${esc(p.condition)} ${esc(p.customerName||"Unknown")} ${p.phone?`${esc(p.phone)} `:""}${idCell(p)}
${fmtDate(p.soldAt)} ${esc(p.paymentMethod||"—")}${p.paymentReference?`${esc(p.paymentReference)} `:""} ${p.salePrice?money.format(p.salePrice):"—"} View ${state.user.role==="admin"?`Void & restock Delete `:""}
`; }
+function saleRow(p) { return `${esc(p.model)} ${esc(p.manufacturer)} · ${esc(primaryId(p))} ${esc(p.customerName||"Unknown")} ${p.phone?`${esc(p.phone)} `:""}${fmtDate(p.soldAt)} ${esc(p.paymentMethod||"—")}${p.paymentReference?`${esc(p.paymentReference)} `:""} ${p.salePrice?money.format(p.salePrice):"—"} ${warrantyBadge(p)} View ${state.user.role==="admin"?`Void & restock Delete `:""}
`; }
function customerRow(c) { return `${esc(c.name)} ${esc(c.phone||"—")} ${c.count} ${fmtDate(c.lastPurchase)} ${c.total?money.format(c.total):"—"} View customer `; }
function receiveForm() {
@@ -71,21 +78,23 @@ function receiveForm() {
}
function sellForm(id="") {
const available=state.products.filter(p=>p.status==="available");
- openModal(`Record a sale Enter the customer, payment, and shipped-to details.
`);
+ openModal(`Record a sale Enter the customer, payment, fulfillment, and warranty details.
Product${available.map(p=>`${esc(p.model)} — ${esc(primaryId(p))} `).join("")} Customer name Cell phone
Payment methodChoose a method Cash Venmo PayPal Payment reference (optional)
Sale price Date sold
Delivery methodChoose a method Shipped Dropped Off Installed At Meet Warranty${warrantyOptions()}
Transaction notes (optional) Cancel Complete sale
`);
+ bindFulfillment($("#sellForm"));
$("#sellForm").onsubmit=async e=>{ e.preventDefault(); const data=Object.fromEntries(new FormData(e.currentTarget)), productId=data.productId; delete data.productId; await change(`/api/products/${encodeURIComponent(productId)}/sell`,"POST",data,"Sale recorded."); }; $("[data-cancel]").onclick=closeModal;
}
async function submitForm(e,path,message,method="POST"){ e.preventDefault(); await change(path,method,Object.fromEntries(new FormData(e.currentTarget)),message); }
async function change(path,method,body,message){ try{ await api(path,{method,body:body?JSON.stringify(body):undefined}); closeModal(); await load(); toast(message); }catch(e){ toast(e.message); } }
function viewSale(p) {
- const notes=state.user.role==="admin"?`Transaction notes${esc(p.saleNotes||"")} Close Save notes
`:`Transaction notes ${esc(p.saleNotes||"No transaction notes recorded")}
Close
`;
- openModal(`Sale record ${esc(p.model)} · ${fmtDate(p.soldAt)}
Customer ${esc(p.customerName||"Unknown")}
${esc(p.phone||"No phone recorded")}
Payment ${esc(p.paymentMethod||"Not recorded")}
${p.paymentReference?`Reference: ${esc(p.paymentReference)}
`:""}Shipped to ${address(p)?esc(address(p)):"No shipping address recorded"}
${p.shippingNotes?`${esc(p.shippingNotes)}
`:""}Product and sale ${esc(p.manufacturer)} ${esc(p.model)} · ${esc(p.condition)}
${idCell(p)}
Received: ${fmtDate(p.receivedAt)} · Sold: ${fmtDate(p.soldAt)}
Cost: ${p.cost?money.format(p.cost):"—"} · Sale price: ${p.salePrice?money.format(p.salePrice):"—"}
${p.notes?`Inventory notes: ${esc(p.notes)}
`:""} ${notes}`); $("[data-cancel]").onclick=closeModal; if($("#transactionNotesForm"))$("#transactionNotesForm").onsubmit=e=>submitForm(e,`/api/products/${encodeURIComponent(p.id)}`,"Transaction notes saved.","PATCH");
+ const link=trackingUrl(p),fulfillment=p.fulfillmentMethod?`${fulfillmentTitle(p.fulfillmentMethod)} ${p.fulfillmentName?`${esc(p.fulfillmentName)}
`:""}${address(p)?esc(address(p)):"No street address recorded"}
${p.fulfillmentNotes?`${esc(p.fulfillmentNotes)}
`:""}${p.fulfillmentMethod==="Shipped"?`Carrier: ${esc(p.carrier||"Not recorded")}
Tracking: ${p.trackingNumber?(link?`${esc(p.trackingNumber)} `:esc(p.trackingNumber)):"Not entered"}
Delivery status: ${esc(p.deliveryStatus||"Not recorded")}
`:""} `:'';
+ openModal(`Sale record ${esc(p.model)} · ${fmtDate(p.soldAt)}
Customer ${esc(p.customerName||"Unknown")}
${esc(p.phone||"No phone recorded")}
Payment and sale ${esc(p.paymentMethod||"Not recorded")} ${p.paymentReference?` · Reference: ${esc(p.paymentReference)}`:""}
Sale price: ${p.salePrice?money.format(p.salePrice):"—"} · Sold: ${fmtDate(p.soldAt)}
${fulfillment}Warranty ${warrantyBadge(p)}
${p.warrantyName?`${esc(p.warrantyName)}${p.warrantyEndDate?` · Through ${fmtDate(p.warrantyEndDate)}`:""}`:"No warranty was recorded for this sale."}
Product ${esc(p.manufacturer)} ${esc(p.model)} · ${esc(p.condition)}
${idCell(p)}
Received: ${fmtDate(p.receivedAt)} · Cost: ${p.cost?money.format(p.cost):"—"}
${p.notes?`Inventory notes: ${esc(p.notes)}
`:""}Transaction notes ${esc(p.saleNotes||"No transaction notes recorded")}
Close ${state.user.role==="admin"?'Edit sale details ':""}
`); $("[data-cancel]").onclick=closeModal;if($("#editSaleDetails"))$("#editSaleDetails").onclick=()=>editSaleForm(p);
}
+function editSaleForm(p){const historicalWarranty=p.warrantyPresetId&&!state.warranties.some(w=>w.id===p.warrantyPresetId)?`${esc(p.warrantyName)} (Archived) `:"";openModal(`Edit sale details Update fulfillment, tracking, warranty, or transaction notes.
Delivery methodChoose a method ${["Shipped","Dropped Off","Installed At","Meet"].map(x=>`${x} `).join("")} Warranty${historicalWarranty}${warrantyOptions(p.warrantyPresetId)}
Transaction notes${esc(p.saleNotes||"")} Cancel Save changes
`);bindFulfillment($("#editSaleForm"),p);$("[data-cancel]").onclick=()=>viewSale(p);$("#editSaleForm").onsubmit=async e=>{e.preventDefault();try{const updated=await api(`/api/products/${encodeURIComponent(p.id)}`,{method:"PATCH",body:JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)))});await load();toast("Sale details updated.");viewSale(updated);}catch(error){toast(error.message);}};}
async function viewCustomer(p) {
if(!p.customerId) return viewSale(p);
try { const c=await api(`/api/customers/${encodeURIComponent(p.customerId)}`), addr=[c.address1,c.address2,[c.city,c.state].filter(Boolean).join(", "),c.zip].filter(Boolean).join(" · ");
const noteForm=state.user.role==="admin"?`CategoryGeneral Support Follow-up New note
Add note
`:"";
- openModal(`${esc(c.name)} Customer record, support notes, and purchase history.
${esc(c.phone||"No phone recorded")}
${addr?esc(addr):"No shipping address recorded"}
${c.shippingNotes?`${esc(c.shippingNotes)}
`:""}Customer notes ${noteForm}${c.notes.length?c.notes.map(n=>`${esc(n.category)} ${fmtDateTime(n.createdAt)}
${esc(n.note)}
${state.user.role==="admin"?`Delete `:""} `).join(""):"No customer notes yet.
"}Purchases (${c.purchases.length}) ${c.purchases.map(s=>`${esc(s.model)} · ${esc(primaryId(s))}${fmtDate(s.soldAt)} · ${s.salePrice?money.format(s.salePrice):"Price not recorded"}
View sale `).join("")}Close
`);
+ openModal(`${esc(c.name)} Customer record, support notes, and purchase history.
${esc(c.phone||"No phone recorded")}
${addr?esc(addr):"No permanent address recorded"}
${c.shippingNotes?`${esc(c.shippingNotes)}
`:""}Customer notes ${noteForm}${c.notes.length?c.notes.map(n=>`${esc(n.category)} ${fmtDateTime(n.createdAt)}
${esc(n.note)}
${state.user.role==="admin"?`Delete `:""} `).join(""):"No customer notes yet.
"}Purchases (${c.purchases.length}) ${c.purchases.map(s=>`${esc(s.model)} · ${esc(primaryId(s))}${fmtDate(s.soldAt)} · ${s.salePrice?money.format(s.salePrice):"Price not recorded"}
${warrantyBadge(s)}
View sale `).join("")}Close
`);
$("[data-cancel]").onclick=closeModal; document.querySelectorAll("[data-customer-sale]").forEach(b=>b.onclick=()=>viewSale(c.purchases.find(s=>s.id===b.dataset.customerSale)));
if($("#customerNoteForm"))$("#customerNoteForm").onsubmit=async e=>{e.preventDefault();try{await api(`/api/customers/${encodeURIComponent(c.id)}/notes`,{method:"POST",body:JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)))});await viewCustomer(p);toast("Customer note added.");}catch(error){toast(error.message);}};
document.querySelectorAll("[data-delete-note]").forEach(b=>b.onclick=async()=>{if(!confirm("Delete this customer note?"))return;try{await api(`/api/customers/${encodeURIComponent(c.id)}/notes/${encodeURIComponent(b.dataset.deleteNote)}`,{method:"DELETE"});await viewCustomer(p);toast("Customer note deleted.");}catch(error){toast(error.message);}});
@@ -93,7 +102,7 @@ async function viewCustomer(p) {
}
function restockForm(p){ openModal(`Void sale & restock Return ${esc(p.model)} to inventory.
ConditionUsed Refurbished New Return date Cancel Restock product
`); $("[data-cancel]").onclick=closeModal; $("#restockForm").onsubmit=e=>submitForm(e,`/api/products/${encodeURIComponent(p.id)}/restock`,"Product restocked."); }
-async function load(){ [state.products,state.models]=await Promise.all([api("/api/products"),api("/api/models")]); render(); }
+async function load(){ [state.products,state.models,state.warranties]=await Promise.all([api("/api/products"),api("/api/models"),api("/api/warranties")]); render(); }
async function renderAdmin(){
const panel=$("#adminPanel"); panel.innerHTML='Loading administration…
';
try{const [backups,users,audit]=await Promise.all([api("/api/admin/backups"),api("/api/admin/users"),api("/api/admin/audit")]);panel.innerHTML=`Database backups Create snapshots inside /data/backups, download an off-server copy, or restore a previous database.
Create backup now Restore uploaded backup
Restore replaces the active database and all user accounts. Your current password is required. Everyone will be signed out afterward.
${backups.length?backups.map(b=>`
${esc(b.name)} ${fmtDateTime(b.createdAt)} · ${(b.size/1024).toFixed(1)} KB
`).join(""):"
No local backups yet.
"}
Audit log The latest 250 security and data-changing events. Audit entries cannot be edited or deleted.
${audit.map(a=>`
${esc(a.username)} ${esc(a.action.replaceAll("_"," "))} ${fmtDateTime(a.createdAt)}${a.target?` · ${esc(a.target)}`:""}${a.details?` · ${esc(a.details)}`:""}${a.ipAddress?` · ${esc(a.ipAddress)}`:""} `).join("")||"
No audit events yet.
"}
`;
@@ -104,6 +113,13 @@ async function renderAdmin(){
document.querySelectorAll("[data-rename-model]").forEach(b=>b.onclick=async()=>{const name=prompt(`Rename ${b.dataset.name}:`,b.dataset.name);if(name===null||name.trim()===b.dataset.name)return;try{await api(`/api/admin/models/${encodeURIComponent(b.dataset.renameModel)}`,{method:"PATCH",body:JSON.stringify({name})});await finishModelChange("Model renamed.");}catch(error){toast(error.message);}});
document.querySelectorAll("[data-toggle-model]").forEach(b=>b.onclick=async()=>{const active=b.dataset.active==="true";if(active&&!confirm(`Archive ${b.dataset.name}? It will no longer appear for newly received products. Existing records remain available (${b.dataset.available} available, ${b.dataset.sold} sold).`))return;try{await api(`/api/admin/models/${encodeURIComponent(b.dataset.toggleModel)}`,{method:"PATCH",body:JSON.stringify({active:!active})});await finishModelChange(active?"Model archived.":"Model reactivated.");}catch(error){toast(error.message);}});
document.querySelectorAll("[data-delete-model]").forEach(b=>b.onclick=async()=>{if(!confirm(`Permanently delete unused model ${b.dataset.name}?`))return;try{await api(`/api/admin/models/${encodeURIComponent(b.dataset.deleteModel)}`,{method:"DELETE"});await finishModelChange("Unused model deleted.");}catch(error){toast(error.message);}});
+ const warranties=await api("/api/admin/warranties"),warrantySection=document.createElement("section");warrantySection.className="admin-section warranty-section";warrantySection.innerHTML=`Warranty periods Choose the default offered on new sales. Used periods are preserved as sale snapshots and can be archived, but not changed or deleted.
Name Duration UnitDays Months Years Add warranty ${warranties.map(w=>`
${esc(w.name)} ${w.isDefault?'Default ':`${w.active?"Active":"Archived"} `}
${w.durationValue} ${esc(w.durationUnit)} · ${w.usageCount} sale${w.usageCount===1?"":"s"} ${!w.isDefault&&w.active?`Make default `:""}${w.usageCount===0?`Edit `:""}${!w.isDefault?`${w.active?"Archive":"Reactivate"} `:""}${w.usageCount===0&&!w.isDefault?`Delete `:""}
`).join("")}
`;modelSection.insertAdjacentElement("afterend",warrantySection);
+ const finishWarrantyChange=async message=>{state.warranties=await api("/api/warranties");toast(message);renderAdmin();};
+ $("#createWarrantyForm").onsubmit=async e=>{e.preventDefault();try{await api("/api/admin/warranties",{method:"POST",body:JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)))});await finishWarrantyChange("Warranty period added.");}catch(error){toast(error.message);}};
+ document.querySelectorAll("[data-default-warranty]").forEach(b=>b.onclick=async()=>{try{await api(`/api/admin/warranties/${encodeURIComponent(b.dataset.defaultWarranty)}`,{method:"PATCH",body:JSON.stringify({isDefault:true})});await finishWarrantyChange(`${b.dataset.name} is now the default warranty.`);}catch(error){toast(error.message);}});
+ document.querySelectorAll("[data-edit-warranty]").forEach(b=>b.onclick=async()=>{const name=prompt("Warranty name:",b.dataset.name);if(name===null)return;const value=prompt("Duration value:",b.dataset.value);if(value===null)return;const unit=prompt("Duration unit (days, months, or years):",b.dataset.unit);if(unit===null)return;try{await api(`/api/admin/warranties/${encodeURIComponent(b.dataset.editWarranty)}`,{method:"PATCH",body:JSON.stringify({name,durationValue:Number(value),durationUnit:unit.toLowerCase()})});await finishWarrantyChange("Warranty period updated.");}catch(error){toast(error.message);}});
+ document.querySelectorAll("[data-toggle-warranty]").forEach(b=>b.onclick=async()=>{const active=b.dataset.active==="true";if(active&&!confirm(`Archive ${b.dataset.name}? Existing sales will retain it.`))return;try{await api(`/api/admin/warranties/${encodeURIComponent(b.dataset.toggleWarranty)}`,{method:"PATCH",body:JSON.stringify({active:!active})});await finishWarrantyChange(active?"Warranty period archived.":"Warranty period reactivated.");}catch(error){toast(error.message);}});
+ document.querySelectorAll("[data-delete-warranty]").forEach(b=>b.onclick=async()=>{if(!confirm(`Permanently delete unused warranty ${b.dataset.name}?`))return;try{await api(`/api/admin/warranties/${encodeURIComponent(b.dataset.deleteWarranty)}`,{method:"DELETE"});await finishWarrantyChange("Unused warranty period deleted.");}catch(error){toast(error.message);}});
$("#createUserForm").onsubmit=async e=>{e.preventDefault();try{await api("/api/admin/users",{method:"POST",body:JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)))});toast("User created.");renderAdmin();}catch(error){toast(error.message);}};
document.querySelectorAll("[data-role-user]").forEach(b=>b.onclick=async()=>{try{await api(`/api/admin/users/${b.dataset.roleUser}`,{method:"PATCH",body:JSON.stringify({role:b.dataset.role==="admin"?"readonly":"admin"})});toast("User role updated.");renderAdmin();}catch(e){toast(e.message);}});
document.querySelectorAll("[data-toggle-user]").forEach(b=>b.onclick=async()=>{try{await api(`/api/admin/users/${b.dataset.toggleUser}`,{method:"PATCH",body:JSON.stringify({enabled:b.dataset.enabled!=="true"})});toast("User status updated.");renderAdmin();}catch(e){toast(e.message);}});
diff --git a/public/extras.css b/public/extras.css
index 053500f..403bbe0 100644
--- a/public/extras.css
+++ b/public/extras.css
@@ -6,3 +6,4 @@
#adminPanel{padding:24px}.admin-page{display:grid;gap:20px}.admin-page h2{margin:0 0 5px}.admin-page p{color:#667085}.admin-actions{display:flex;gap:10px;flex-wrap:wrap}.upload-backup{display:inline-flex;margin:0;background:#fff;border:1px solid #d7deea;border-radius:9px;padding:10px 14px;color:#27344a;cursor:pointer}.upload-backup input{display:none}.backup-warning{padding:13px 15px;border-radius:9px;background:#fff7ed;color:#9a3412;border:1px solid #fed7aa}.backup-list{border:1px solid #e3e7ef;border-radius:10px}.backup-list article{display:flex;justify-content:space-between;align-items:center;gap:15px;padding:14px;border-bottom:1px solid #edf0f5}.backup-list article:last-child{border-bottom:0}.backup-list article small{display:block;color:#7b8799;margin-top:4px}.backup-list article>div:last-child{display:flex;gap:8px}.backup-list a.button{display:inline-flex;align-items:center;background:#fff;border:1px solid #d7deea;border-radius:9px;padding:10px 14px;color:#27344a;font-weight:650;text-decoration:none}.backup-list .delete-backup{background:#fff0f0;color:#c62828}.restart-message{text-align:center;padding:60px 20px}@media(max-width:760px){.backup-list article{align-items:flex-start;flex-direction:column}}
.auth-pending #appShell,.auth-required #appShell{display:none}.role-admin #authScreen,.role-readonly #authScreen{display:none}.auth-screen{min-height:100vh;display:grid;place-items:center;padding:24px;background:linear-gradient(145deg,#eef4ff,#f8fafc)}.auth-card{width:min(430px,100%);background:#fff;border:1px solid #dfe5ef;border-radius:18px;box-shadow:0 22px 60px #17203320;padding:34px}.auth-brand{margin-bottom:28px}.auth-card h1{margin:0 0 7px}.auth-card>div>p{color:#68758b}.auth-submit{width:100%;margin-top:14px}.auth-message{background:#fff0f0;color:#a51d1d;border:1px solid #fecaca;border-radius:8px;padding:10px 12px;margin:14px 0}.auth-logout{width:100%;margin-top:10px}.user-menu{display:flex;align-items:center;gap:10px;border-left:1px solid #e3e7ef;padding-left:18px}.user-menu span{display:grid;font-size:13px}.user-menu small{color:#758197}.admin-section{display:grid;gap:16px;border-bottom:1px solid #e3e7ef;padding-bottom:28px}.admin-section:last-child{border-bottom:0}.inline-user-form{display:grid;grid-template-columns:1fr 1fr 180px auto;gap:10px;align-items:end}.inline-user-form label{margin:0}.user-list,.audit-list{border:1px solid #e3e7ef;border-radius:10px;overflow:hidden}.user-list article{display:flex;justify-content:space-between;align-items:center;gap:15px;padding:14px;border-bottom:1px solid #edf0f5}.user-list article:last-child{border-bottom:0}.user-list small{display:block;color:#7b8799;margin-top:4px}.user-list article>div:last-child{display:flex;gap:7px;flex-wrap:wrap}.delete-backup{background:#fff0f0;color:#c62828}.audit-list{max-height:420px;overflow:auto}.audit-list article{display:grid;grid-template-columns:140px 180px 1fr;gap:12px;padding:10px 13px;border-bottom:1px solid #edf0f5;font-size:13px}.audit-list span{text-transform:capitalize}.audit-list small{color:#7b8799}@media(max-width:900px){.inline-user-form{grid-template-columns:1fr 1fr}.user-list article{align-items:flex-start;flex-direction:column}.audit-list article{grid-template-columns:1fr}.user-menu span{display:none}}@media(max-width:600px){.inline-user-form{grid-template-columns:1fr}.user-menu{padding-left:5px}.user-menu button{font-size:12px;padding:8px}.auth-card{padding:25px}}
.inline-model-form{display:grid;grid-template-columns:minmax(220px,1fr) auto;gap:10px;align-items:end}.inline-model-form label{margin:0}.model-list{border:1px solid #e3e7ef;border-radius:10px;overflow:hidden}.model-list article{display:flex;justify-content:space-between;align-items:center;gap:15px;padding:14px;border-bottom:1px solid #edf0f5}.model-list article:last-child{border-bottom:0}.model-summary>div{display:flex;align-items:center;gap:9px}.model-summary small{display:block;color:#7b8799;margin-top:5px}.model-status{display:inline-flex;border-radius:99px;padding:3px 8px;font-size:11px;font-weight:750}.model-status.active{background:#dcfce7;color:#166534}.model-status.archived{background:#eef1f5;color:#526071}.model-actions{display:flex;gap:7px;flex-wrap:wrap}@media(max-width:760px){.inline-model-form{grid-template-columns:1fr}.model-list article{align-items:stretch;flex-direction:column}.model-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}.model-actions button{text-align:center}}
+.conditional-fields{margin:2px 0 14px;padding:16px;border:1px solid #e3e7ef;border-radius:11px;background:#f8fafc}.conditional-fields h3{margin:0 0 12px}.conditional-hint{margin:4px 0 16px;padding:13px;border:1px dashed #d7deea;border-radius:9px;color:#68758b}.field-help{display:block;margin-top:-8px;color:#68758b}.warranty-pill{display:inline-flex;align-items:center;border-radius:99px;padding:5px 9px;font-size:12px;font-weight:750;white-space:nowrap}.warranty-pill.in-warranty{background:#dcfce7;color:#166534}.warranty-pill.expired{background:#fee2e2;color:#b91c1c}.warranty-pill.neutral{background:#eef1f5;color:#526071}.inline-warranty-form{display:grid;grid-template-columns:minmax(180px,1fr) 130px 150px auto;gap:10px;align-items:end}.inline-warranty-form label{margin:0}.warranty-list{border:1px solid #e3e7ef;border-radius:10px;overflow:hidden}.warranty-list article{display:flex;justify-content:space-between;align-items:center;gap:15px;padding:14px;border-bottom:1px solid #edf0f5}.warranty-list article:last-child{border-bottom:0}@media(max-width:760px){.inline-warranty-form{grid-template-columns:1fr}.warranty-list article{align-items:stretch;flex-direction:column}}
diff --git a/public/theme.css b/public/theme.css
index bb15b3f..d095c23 100644
--- a/public/theme.css
+++ b/public/theme.css
@@ -23,8 +23,9 @@ th{background:var(--surface-table);color:var(--muted-2)}th,td,.records nav,.back
.records nav .tab,.customer-link{color:var(--muted)}.records nav .tab.active{color:var(--blue);border-color:var(--blue)}.tab i{background:var(--surface-soft)}
.row-actions button,.customer-purchases button{background:var(--blue-soft);color:var(--blue-text)}.row-actions .danger,.customer-notes article>button,.delete-backup,.backup-list .delete-backup{background:var(--danger-soft);color:var(--danger)}
.pill{background:var(--green-soft);color:var(--green)}.detail-card{background:var(--surface-soft);border-color:var(--border)}
-.customer-notes article,.backup-list,.user-list,.model-list,.audit-list{border-color:var(--border)}
+.customer-notes article,.backup-list,.user-list,.model-list,.warranty-list,.audit-list{border-color:var(--border)}
.model-list article{border-color:var(--border)}.model-summary small{color:var(--muted)}.model-status.active{background:var(--green-soft);color:var(--green)}.model-status.archived{background:var(--surface-soft);color:var(--muted)}
+.warranty-list article,.conditional-fields{border-color:var(--border)}.conditional-fields{background:var(--surface-soft)}.conditional-hint{border-color:var(--border-strong);color:var(--muted)}.field-help{color:var(--muted)}.warranty-pill.in-warranty{background:var(--green-soft);color:var(--green)}.warranty-pill.expired{background:var(--danger-soft);color:var(--danger)}.warranty-pill.neutral{background:var(--surface-soft);color:var(--muted)}
.note-category{background:var(--blue-soft);color:var(--blue-text)}.backup-warning{background:var(--warning-soft);border-color:color-mix(in srgb,var(--warning) 40%,transparent);color:var(--warning)}
.auth-screen{background:linear-gradient(145deg,color-mix(in srgb,var(--blue) 10%,var(--page)),var(--page))}.auth-card{box-shadow:0 22px 60px var(--shadow)}
dialog{max-height:min(90dvh,900px);overflow:auto}dialog::backdrop{background:#050914aa}.close{color:var(--muted)}
@@ -40,6 +41,7 @@ button,.button,input,select,textarea{-webkit-tap-highlight-color:transparent}but
.inline-user-form{grid-template-columns:1fr 1fr}.inline-model-form{grid-template-columns:1fr auto}.audit-list article{grid-template-columns:120px 160px 1fr}
}
@media (max-width:820px){
+ html,body{max-width:100%;overflow-x:hidden}input,select,textarea{font-size:16px!important}.auth-screen{min-height:100dvh;width:100%;overflow-x:hidden}.auth-card{max-width:100%}
header{position:relative;display:grid;grid-template-columns:auto 1fr auto;height:auto;padding:12px max(14px,env(safe-area-inset-right)) 12px max(14px,env(safe-area-inset-left));gap:10px}
header .brand{grid-column:1}.user-menu{grid-column:2/4;grid-row:1;margin-left:auto!important;min-width:0!important;padding-left:0;border-left:0}.user-menu #currentUser{display:grid!important;width:auto!important;min-width:78px;padding:5px 7px}.theme-control{grid-template-columns:1fr;margin-left:0;padding-left:6px}.theme-control>span{font-size:10px;text-align:center}
header #search{grid-column:1/-1;grid-row:2;order:initial}.actions{grid-column:1/-1;grid-row:3;width:100%;margin:0}.actions button{font-size:14px!important;flex:1}.actions button::first-letter{font-size:inherit!important}
@@ -48,7 +50,7 @@ button,.button,input,select,textarea{-webkit-tap-highlight-color:transparent}but
.table-wrap{overflow:visible}table{display:block;min-width:0}thead{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}tbody{display:grid;gap:10px;padding:10px;background:var(--page)}tr{display:grid;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden}td{display:grid;grid-template-columns:minmax(92px,34%) 1fr;gap:10px;align-items:start;padding:11px 13px;border-bottom:1px solid var(--border);overflow-wrap:anywhere}td:last-child{border-bottom:0}td::before{content:attr(data-label);font-size:11px;font-weight:750;text-transform:uppercase;letter-spacing:.04em;color:var(--muted-2)}td:empty{display:none}.row-actions{flex-wrap:wrap}.row-actions button{flex:1;min-width:95px}
#empty{background:var(--surface);padding:45px 20px}#pagination{background:var(--surface);padding:14px 12px}.address-grid{grid-template-columns:1fr}.form-row,.scan{grid-template-columns:1fr}
dialog{width:calc(100vw - 20px);max-height:calc(100dvh - 20px);padding:22px 18px;margin:10px}.form-actions{flex-wrap:wrap}.form-actions button{min-width:110px}
- #adminPanel{padding:15px 12px}.admin-page{gap:26px}.admin-section{gap:13px}.inline-user-form,.inline-model-form{grid-template-columns:1fr}.user-list article,.model-list article,.backup-list article{align-items:stretch}.user-list article>div:last-child,.model-actions,.backup-list article>div:last-child{display:grid;grid-template-columns:1fr 1fr}.user-list button,.model-list button,.backup-list button,.backup-list a.button{justify-content:center;text-align:center}.audit-list article{grid-template-columns:1fr;gap:3px}.audit-list{max-height:55vh}
+ #adminPanel{padding:15px 12px}.admin-page{gap:26px}.admin-section{gap:13px}.inline-user-form,.inline-model-form,.inline-warranty-form{grid-template-columns:1fr}.user-list article,.model-list article,.warranty-list article,.backup-list article{align-items:stretch}.user-list article>div:last-child,.model-actions,.backup-list article>div:last-child{display:grid;grid-template-columns:1fr 1fr}.user-list button,.model-list button,.warranty-list button,.backup-list button,.backup-list a.button{justify-content:center;text-align:center}.audit-list article{grid-template-columns:1fr;gap:3px}.audit-list{max-height:55vh}
.auth-screen{padding:16px}.auth-card{padding:25px 20px}.auth-brand span{display:inline}
}
@media (max-width:430px){