// ── DATA ─────────────────────────────────────────────────────────────
const EMPLOYEES = ["Kate","Venus","Mohammed","Arcilie"];
const MASSAGE_TYPES = ["Relax Massage","Medical Massage","Lymphatic Massage","Swedish Massage","Thai Massage","Pregnancy Massage","After Birth Massage","Candle Massage","Pearl VIP Massage","Deep Tissue Massage"];
const SERVICE_CATS = ["Massage","Nails","Facial","Waxing","Combo Package"];
const EXPENSE_CATS = ["Supplies","Equipment","Utilities","Marketing","Other"];
const PRICES = {"Relax Massage":300,"Medical Massage":380,"Lymphatic Massage":420,"Swedish Massage":320,"Thai Massage":350,"Pregnancy Massage":400,"After Birth Massage":400,"Candle Massage":380,"Pearl VIP Massage":600,"Deep Tissue Massage":450,"Nails":180,"Facial":280,"Waxing":150,"Combo Package":550};
const DELIVERY = {"No Delivery":0,"Riyadh - Near (0-10km)":50,"Riyadh - Mid (10-20km)":80,"Riyadh - Far (20-30km)":120,"Outside Riyadh":200};
const PAY_METHODS = ["Cash","Card","Bank Transfer","STC Pay","Mada"];
const MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
const VAT = 0.15;
const ADVANCE = 100;
const today = new Date().toISOString().split("T")[0];
let db = {
employees: EMPLOYEES.map(n=>({id:n,name:n,salary:0})),
clients: [
{id:1,name:"Sarah Al-Zahrani",phone:"0501234567",cat:"Massage",mtype:"Relax Massage",visits:5,notes:"Prefers light pressure"},
{id:2,name:"Mia Chen",phone:"0559876543",cat:"Nails",mtype:"",visits:3,notes:"Gel manicure"},
],
expenses: [
{id:1,cat:"Supplies",amount:250,note:"Nail polish set",date:"2026-04-01",emp:""},
{id:2,cat:"Utilities",amount:180,note:"Electric bill",date:"2026-04-05",emp:""},
],
appts: [
{id:1,client:"Sarah Al-Zahrani",cat:"Massage",mtype:"Relax Massage",emp:"Kate",date:"2026-04-10",time:"10:00",status:"completed",price:300,delzone:"No Delivery",delfee:0,discount:0,advance:true},
{id:2,client:"Mia Chen",cat:"Nails",mtype:"",emp:"Venus",date:"2026-04-12",time:"14:00",status:"completed",price:180,delzone:"No Delivery",delfee:0,discount:0,advance:true},
{id:3,client:"Sarah Al-Zahrani",cat:"Massage",mtype:"Swedish Massage",emp:"Kate",date:today,time:"11:00",status:"confirmed",price:320,delzone:"Riyadh - Near (0-10km)",delfee:50,discount:0,advance:true},
],
revenue: [
{id:1,client:"Sarah Al-Zahrani",cat:"Massage",mtype:"Relax Massage",amount:300,delfee:0,discount:0,date:"2026-04-10",method:"Cash",advance:true},
{id:2,client:"Mia Chen",cat:"Nails",mtype:"",amount:180,delfee:0,discount:0,date:"2026-04-12",method:"Card",advance:true},
],
promos: [
{id:1,name:"Spring Special",type:"percent",value:20,services:["Relax Massage"],active:true,expiry:"2026-05-01",note:"Spring offer"},
],
giftCards: [
{id:1,code:"PEARL-2026-0001",recipientName:"Hessa Al-Otaibi",recipientPhone:"0501112233",gifterName:"Reem Al-Otaibi",value:300,balance:300,service:"Any Service",issued:today,expiry:"2026-12-31",note:"Birthday gift"},
],
reminders: [
{id:1,client:"Sarah Al-Zahrani",phone:"0501234567",type:"Follow-up",date:today,time:"10:00",note:"Check in after massage",done:false},
{id:2,client:"Mia Chen",phone:"0559876543",type:"Appointment Reminder",date:today,time:"13:00",note:"Confirm tomorrow's nails",done:false},
],
dailyIncome: [],
stockLog: [],
transportZones: [
{id:1, name:"No Delivery", fee:0},
{id:2, name:"Riyadh - Near (0-10km)", fee:50},
{id:3, name:"Riyadh - Mid (10-20km)", fee:80},
{id:4, name:"Riyadh - Far (20-30km)", fee:120},
{id:5, name:"Outside Riyadh", fee:200},
],
stock: [
{id:1, name:"Panty & Bra Set", unit:"Package", pkgQty:1, totalPkg:0, pcsPerClient:1, alertAt:5, notes:"1 set per client per session"},
{id:2, name:"Bed Sheet Roll", unit:"Roll", pkgQty:100, totalPkg:0, pcsPerClient:2, alertAt:1, notes:"2 sheets per client (cover+base)"},
{id:3, name:"Eye Mask", unit:"Pack", pkgQty:50, totalPkg:0, pcsPerClient:1, alertAt:1, notes:"1 mask per client"},
{id:4, name:"Massage Oil", unit:"Gallon", pkgQty:250, totalPkg:0, pcsPerClient:30, alertAt:1, notes:"~30ml per session"},
{id:5, name:"Slippers", unit:"Box", pkgQty:100, totalPkg:0, pcsPerClient:1, alertAt:1, notes:"1 pair per client"},
{id:6, name:"Big Disposable Towel", unit:"Pack", pkgQty:50, totalPkg:0, pcsPerClient:2, alertAt:1, notes:"2 per client per session"},
{id:7, name:"Short Disposable Towel", unit:"Pack", pkgQty:50, totalPkg:0, pcsPerClient:2, alertAt:1, notes:"2 per client per session"},
],
};
let state = { tab:"dashboard", salaryTarget:null, invoiceClientId:null, rmFilter:"All", gcRedeemAmounts:{} };
// ── HELPERS ──────────────────────────────────────────────────────────
const SAR = n => `${(+n||0).toLocaleString("en-SA",{minimumFractionDigits:2,maximumFractionDigits:2})} ﷼`;
const eid = id => document.getElementById(id);
const genCode = () => `PEARL-${new Date().getFullYear()}-${Math.floor(1000+Math.random()*9000)}`;
const svcLabel = (cat,mtype) => cat==="Massage"&&mtype ? mtype : cat;
const bcls = cat => cat==="Massage"?"bm":cat==="Nails"?"bn":cat==="Facial"?"bf":"bo";
const scol = s => s==="completed"?"var(--green)":s==="confirmed"?"var(--gold)":s==="cancelled"?"var(--red)":"var(--purple)";
const rmTypeCls = t => t==="Appointment Reminder"?"rm-type-appt":t==="Follow-up"?"rm-type-followup":t==="Birthday"?"rm-type-birthday":"rm-type-general";
function totals(){
const tExpB = db.expenses.reduce((s,e)=>s+e.amount,0);
const tExpV = tExpB*VAT; const tExp = tExpB+tExpV;
const tSal = db.employees.reduce((s,e)=>s+e.salary,0);
const tRevB = db.revenue.reduce((s,r)=>s+r.amount,0);
const tDel = db.revenue.reduce((s,r)=>s+(r.delfee||0),0);
const tRevV = tRevB*VAT; const tRev = tRevB+tRevV+tDel;
const net = tRev-tExp-tSal;
return {tExpB,tExpV,tExp,tSal,tRevB,tDel,tRevV,tRev,net};
}
// ── MODALS ───────────────────────────────────────────────────────────
function openModal(id){ eid(id).classList.add("open"); }
function closeModal(id){ eid(id).classList.remove("open"); }
document.querySelectorAll(".ov").forEach(ov=>{
ov.addEventListener("click",e=>{ if(e.target===ov) ov.classList.remove("open"); });
});
// ── TABS ─────────────────────────────────────────────────────────────
const TABS = [
["dashboard","🏠 Home"],["appointments","📅 Appts"],["daily","📋 Daily"],
["revenue","💰 Revenue"],["employees","👥 Staff"],["expenses","💸 Expenses"],
["clients","👤 Clients"],["promos","🎁 Promos"],["giftcards","🎀 Gift Cards"],
["reminders","🔔 Reminders"],["transport","🚗 Transport"],["stock","📦 Stock"],["reports","📊 Reports"]
];
function renderNav(){
eid("nav").innerHTML = TABS.map(([k,l])=>`${l} `).join("")
+ `›
`;
}
function switchTab(t){ state.tab=t; renderNav(); TABS.forEach(([k])=>{ eid("tab-"+k).className="tab-section"+(k===t?" active":""); }); renderTab(t); }
function renderTab(t){
if(t==="dashboard") renderDashboard();
else if(t==="appointments") renderAppointments();
else if(t==="daily") renderDaily();
else if(t==="revenue") renderRevenue();
else if(t==="employees") renderEmployees();
else if(t==="expenses") renderExpenses();
else if(t==="clients") renderClients();
else if(t==="promos") renderPromos();
else if(t==="giftcards") renderGiftCards();
else if(t==="reminders") renderReminders();
else if(t==="transport") renderTransport();
else if(t==="stock") renderStock();
else if(t==="reports") renderReports();
}
// ── HEADER STATS ─────────────────────────────────────────────────────
function renderHeader(){
const {tRev,tExp,tSal,net} = totals();
eid("hstats").innerHTML = [
{l:"Revenue",v:SAR(tRev),c:"var(--green)"},
{l:"Outflow",v:SAR(tExp+tSal),c:"var(--gold-dk)"},
{l:"Net Profit",v:SAR(net),c:net>=0?"var(--green)":"var(--red)"},
].map(s=>`${s.l} ${s.v}
`).join("");
}
// ── STAT CARD HTML ────────────────────────────────────────────────────
function sc(label,value,cls,sub=""){
return `${label} ${value}
${sub?`
${sub}
`:""}
`;
}
// ── DASHBOARD ─────────────────────────────────────────────────────────
function renderDashboard(){
const {tRev,tExp,tSal,tDel,net} = totals();
const todayAppts = db.appts.filter(a=>a.date===today);
const todayDI = db.dailyIncome.filter(d=>d.date===today);
eid("tab-dashboard").innerHTML = `
Overview
${sc("Revenue (incl. VAT)",SAR(tRev),"c-green")}
${sc("Expenses (incl. VAT)",SAR(tExp),"c-gold")}
${sc("Salaries",SAR(tSal),"c-gold")}
${sc("Delivery Income",SAR(tDel),"c-green")}
${sc("Net Profit",SAR(net),net>=0?"c-green":"c-red")}
${sc("Total Clients",db.clients.length,"c-gold",`${db.clients.reduce((s,c)=>s+c.visits,0)} total visits`)}
${sc("Appointments",db.appts.length,"c-gold",`${db.appts.filter(a=>a.status==="confirmed").length} upcoming`)}
${sc("Gift Cards",db.giftCards.length,"c-gold",`${db.giftCards.filter(g=>!g.redeemed).length} active`)}
${sc("Reminders",db.reminders.filter(r=>!r.done).length,"c-gold",`${db.reminders.filter(r=>r.date===today&&!r.done).length} due today`)}
Today's Appointments
${todayAppts.length===0?`
No appointments scheduled today
`:
todayAppts.map(a=>`
${a.client}
${a.time} · ${svcLabel(a.cat,a.mtype)} · ${a.emp}
${a.status}
`).join("")
}
Today's Income
+ Add Entry
${todayDI.length===0?`
No entries today
`:
todayDI.map(d=>{const p=d.total-d.vat-d.exp; return `
${d.client||"—"}
${svcLabel(d.cat,d.mtype)}
${SAR(d.total)}
Profit: ${SAR(p)}
`}).join("")
}
`;
}
// ── APPOINTMENTS ──────────────────────────────────────────────────────
function renderAppointments(){
const statuses = ["confirmed","pending","completed","cancelled"];
let html = ``;
statuses.forEach(st=>{
const g = db.appts.filter(a=>a.status===st).sort((a,b)=>a.date.localeCompare(b.date));
if(!g.length) return;
html += `
${st} (${g.length})
Date/Time Client Service Type Staff Price Delivery Discount Advance Status
${g.map(a=>`
${a.date} ${a.time}
${a.client}
${a.cat}
${a.mtype||"—"}
${a.emp}
${SAR(a.price)}
${a.delfee>0?SAR(a.delfee):"—"}
${a.discount>0?"-"+SAR(a.discount):"—"}
${a.advance?"✓ Paid":"Pending"}
${["confirmed","pending","completed","cancelled"].map(s=>`${s} `).join("")}
✕
`).join("")}
`;
});
if(!db.appts.length) html += `No appointments yet.
`;
eid("tab-appointments").innerHTML = html;
}
// ── DAILY INCOME ──────────────────────────────────────────────────────
function renderDaily(){
const tRev = db.dailyIncome.reduce((s,d)=>s+d.total,0);
const tVAT = db.dailyIncome.reduce((s,d)=>s+d.vat,0);
const tExp = db.dailyIncome.reduce((s,d)=>s+d.exp,0);
const tProfit = tRev-tVAT-tExp;
let html = `
${sc("Total Revenue",SAR(tRev),"c-green")}
${sc("Total VAT",SAR(tVAT),"c-gold")}
${sc("Total Expenses",SAR(tExp),"c-gold")}
${sc("Total Profit",SAR(tProfit),tProfit>=0?"c-green":"c-red")}
Date Client Service Type Total VAT (−) Expenses (−) Delivery Discount Advance Method = Profit
${db.dailyIncome.length===0?`No entries yet — click "+ Add Entry" to start tracking `:
db.dailyIncome.map(d=>{
const profit=d.total-d.vat-d.exp;
return `
${d.date}
${d.client||"—"}
${d.cat}
${d.mtype||"—"}
${SAR(d.total)}
−${SAR(d.vat)}
−${SAR(d.exp)}
${d.delfee>0?SAR(d.delfee):"—"}
${d.discount>0?"-"+SAR(d.discount):"—"}
${d.advance?"✓ "+SAR(ADVANCE):"—"}
${d.method}
${SAR(profit)}
✕
`;
}).join("")
}
`;
if(db.dailyIncome.length>0) html += `
Revenue: ${SAR(tRev)}
−
VAT: ${SAR(tVAT)}
−
Expenses: ${SAR(tExp)}
=
Profit: ${SAR(tProfit)}
`;
eid("tab-daily").innerHTML = html;
}
// ── REVENUE ───────────────────────────────────────────────────────────
function renderRevenue(){
const tRevB = db.revenue.reduce((s,r)=>s+r.amount,0);
const tDel = db.revenue.reduce((s,r)=>s+(r.delfee||0),0);
const tRevV = tRevB*VAT;
const tRev = tRevB+tRevV+tDel;
eid("tab-revenue").innerHTML = `
${sc("Gross Revenue",SAR(tRevB),"c-gold")}
${sc("VAT Collected",SAR(tRevV),"c-gold")}
${sc("Delivery Income",SAR(tDel),"c-green")}
${sc("Total incl. VAT",SAR(tRev),"c-green")}
Date Client Service Type Method Amount Delivery Discount VAT Advance
${db.revenue.length===0?`No revenue logged yet `:
db.revenue.map(r=>`
${r.date}
${r.client}
${r.cat}
${r.mtype||"—"}
${r.method}
${SAR(r.amount)}
${r.delfee>0?"+"+SAR(r.delfee):"—"}
${r.discount>0?"-"+SAR(r.discount):"—"}
${SAR(r.amount*VAT)}
${r.advance?"✓ "+SAR(ADVANCE):"—"}
✕
`).join("")
}
`;
}
// ── EMPLOYEES ─────────────────────────────────────────────────────────
function renderEmployees(){
const staffRev = n => db.appts.filter(a=>a.emp===n&&a.status==="completed").reduce((s,a)=>s+a.price,0);
const staffExp = n => db.expenses.filter(e=>e.emp===n).reduce((s,e)=>s+e.amount,0);
const staffAppts = n => db.appts.filter(a=>a.emp===n).length;
eid("tab-employees").innerHTML = `
Staff & Salaries
${db.employees.map(e=>`
${e.name[0]}
${e.name}
${staffAppts(e.name)} appointments · Spa Artist
Monthly Salary
${SAR(e.salary)}
Edit
Revenue: ${SAR(staffRev(e.name))}
Expenses: ${SAR(staffExp(e.name))}
`).join("")}
`;
}
// ── EXPENSES ──────────────────────────────────────────────────────────
function renderExpenses(){
const {tExpB,tExpV,tExp} = totals();
const catTotals = EXPENSE_CATS.map(c=>({c,t:db.expenses.filter(e=>e.cat===c).reduce((s,e)=>s+e.amount,0)})).filter(x=>x.t>0);
eid("tab-expenses").innerHTML = `
${catTotals.map(x=>`
`).join("")}
Date Category Note Employee Amount
${db.expenses.length===0?`No expenses recorded `:
db.expenses.map(e=>`
${e.date}
${e.cat}
${e.note||"—"}
${e.emp||"—"}
−${SAR(e.amount)}
✕
`).join("")
}
Subtotal: ${SAR(tExpB)} | VAT 15%: ${SAR(tExpV)} | Total: ${SAR(tExp)}
`;
}
// ── CLIENTS ───────────────────────────────────────────────────────────
function renderClients(search=""){
const list = db.clients.filter(c=>!search||c.name.toLowerCase().includes(search.toLowerCase())||c.phone.includes(search));
eid("tab-clients").innerHTML = `
${list.map(c=>`
✕
${c.cat} ${c.mtype?`${c.mtype} `:""}
${c.visits} visits
+
${c.defTransport&&c.defTransport!=="No Delivery"?`
🚗 ${c.defTransport}
`:""}
Generate Invoice
${c.notes?`
${c.notes}
`:""}
`).join("")}
${list.length===0?`
No clients found.
`:""}
`;
}
// ── PROMOTIONS ────────────────────────────────────────────────────────
function renderPromos(){
eid("tab-promos").innerHTML = `
${db.promos.map(p=>`
${p.active?"Active":"Inactive"}
✕
${p.type==="percent"?p.value+"% OFF":SAR(p.value)+" OFF"}
${p.expiry?`
Expires: ${p.expiry}
`:""}
Applies to: ${p.services.length?p.services.join(", "):"All services"}
${p.active?"Deactivate":"Activate"}
`).join("")}
${db.promos.length===0?`
No promotions yet.
`:""}
`;
}
// ── GIFT CARDS ────────────────────────────────────────────────────────
function renderGiftCards(){
const total = db.giftCards.reduce((s,g)=>s+g.value,0);
const balance = db.giftCards.reduce((s,g)=>s+g.balance,0);
eid("tab-giftcards").innerHTML = `
${sc("Total Issued",db.giftCards.length,"c-gold")}
${sc("Total Value",SAR(total),"c-gold")}
${sc("Remaining Balance",SAR(balance),"c-green")}
${sc("Fully Redeemed",db.giftCards.filter(g=>g.redeemed).length,"c-gold")}
${db.giftCards.map(g=>`
✕
PEARL
Home Spa · Gift Card
${g.redeemed?"Redeemed":g.balance
${SAR(g.balance)}
For ${g.recipientName}
${g.code}
Balance
${SAR(g.balance)} / ${SAR(g.value)}
${[["For",g.recipientName],["From",g.gifterName||"—"],["Phone",g.recipientPhone||"—"],["Service",g.service],["Issued",g.issued],["Expires",g.expiry||"No expiry"]].map(([k,v])=>
`
${k} ${v}
`
).join("")}
${g.note?`
${g.note}
`:""}
${!g.redeemed&&g.balance>0?`
Redeem
`:`
${g.redeemed?"Fully redeemed":"Balance depleted"}
`}
`).join("")}
${db.giftCards.length===0?`
No gift cards issued yet.
`:""}
`;
}
// ── REMINDERS ─────────────────────────────────────────────────────────
function renderReminders(){
const filter = state.rmFilter||"All";
const overdue = db.reminders.filter(r=>!r.done&&r.date!r.done&&r.date===today);
const filtered = db.reminders.filter(r=>filter==="All"?true:filter==="Pending"?!r.done:r.done)
.sort((a,b)=>a.date.localeCompare(b.date));
let html = `
${sc("Total",db.reminders.length,"c-gold")}
${sc("Pending",db.reminders.filter(r=>!r.done).length,"c-gold")}
${sc("Due Today",dueToday.length,dueToday.length>0?"c-red":"c-green")}
${sc("Completed",db.reminders.filter(r=>r.done).length,"c-green")}
`;
// Overdue alert
if(overdue.length>0){
html += `
⚠ Overdue (${overdue.length})
${overdue.map(r=>`
${r.client} · ${r.phone}
${r.date} ${r.time} · ${r.type}${r.note?" — "+r.note:""}
Done ✓
`).join("")}
`;
}
// Today alert
if(dueToday.length>0){
html += `
⚡ Due Today (${dueToday.length})
${dueToday.map(r=>`
${r.client} · ${r.phone}
${r.time} · ${r.type}${r.note?" — "+r.note:""}
Done ✓
`).join("")}
`;
}
// Filter tabs
html += `
${["All","Pending","Completed"].map(f=>`${f} `).join("")}
${filtered.map(r=>`
${r.type}
${r.done?`✓ Done `:""}
${!r.done&&r.date===today?`Today `:""}
${!r.done&&r.dateOverdue`:""}
📅 ${r.date} 🕐 ${r.time}
${r.note?`
${r.note}
`:""}
${r.done?"Mark Pending":"Mark Done ✓"}
✕
`).join("")}
${filtered.length===0?`
No reminders in this category.
`:""}
`;
eid("tab-reminders").innerHTML = html;
}
// ── REPORTS ───────────────────────────────────────────────────────────
function renderReports(){
const selMonth = state.reportMonth ?? new Date().getMonth();
const year = new Date().getFullYear();
const mRev = Array(12).fill(0).map((_,m)=>db.revenue.filter(r=>new Date(r.date).getMonth()===m).reduce((s,r)=>s+r.amount,0));
const mExp = Array(12).fill(0).map((_,m)=>db.expenses.filter(e=>new Date(e.date).getMonth()===m).reduce((s,e)=>s+e.amount,0));
const maxB = Math.max(...mRev,...mExp,1);
const staffRev = EMPLOYEES.map(n=>({name:n,appts:db.appts.filter(a=>a.emp===n).length,rev:db.appts.filter(a=>a.emp===n&&a.status==="completed").reduce((s,a)=>s+a.price,0)}));
const totalRevB = db.revenue.reduce((s,r)=>s+r.amount,0);
eid("tab-reports").innerHTML = `
Revenue vs Expenses — ${year}
${MONTHS.map((m,i)=>`
`).join("")}
■ Revenue
■ Expenses
${sc(MONTHS[selMonth]+" Revenue",SAR(mRev[selMonth]),"c-green")}
${sc(MONTHS[selMonth]+" Expenses",SAR(mExp[selMonth]),"c-gold")}
${sc(MONTHS[selMonth]+" Net",SAR(mRev[selMonth]-mExp[selMonth]),mRev[selMonth]>=mExp[selMonth]?"c-green":"c-red")}
Massage Types Performance
${MASSAGE_TYPES.map(mt=>{
const cnt=db.appts.filter(a=>a.mtype===mt).length;
const tot=db.revenue.filter(r=>r.mtype===mt).reduce((s,r)=>s+r.amount,0);
if(!cnt)return "";
return `
${mt}
${SAR(tot)} (${cnt}×)
`;
}).join("")||`
No massage data yet
`}
Staff Performance
${staffRev.map(s=>`
${s.name[0]}
${s.name}
${s.appts} appointments
`).join("")}
`;
}
// ── SERVICE SELECTOR HTML ─────────────────────────────────────────────
function svcSelectorHTML(prefix, cat, mtype){
return `Service Category
${SERVICE_CATS.map(s=>`${s} `).join("")}
Massage Type
${MASSAGE_TYPES.map(m=>`
${m}
${SAR(PRICES[m]||0)}
`).join("")}
`;
}
function onCatChange(prefix){
const cat = eid(prefix+"-cat").value;
eid(prefix+"-mtype-wrap").style.display = cat==="Massage"?"block":"none";
if(prefix==="appt") { eid("appt-price").value = PRICES[cat]||300; }
}
function selectMassage(prefix, mtype){
document.querySelectorAll(`#${prefix}-mtype-wrap .svc-opt`).forEach(el=>{
el.classList.toggle("sel", el.querySelector(".svc-name").textContent===mtype);
});
if(prefix==="appt"){ eid("appt-price").value = PRICES[mtype]||300; }
}
function getSelectedMassage(prefix){
const sel = document.querySelector(`#${prefix}-mtype-wrap .svc-opt.sel`);
return sel ? sel.querySelector(".svc-name").textContent : "";
}
// ── LOGO HTML ─────────────────────────────────────────────────────────
const logoHTML = ``;
// ── MODAL: APPOINTMENT ────────────────────────────────────────────────
function openApptModal(){
eid("md-appt").innerHTML = `
${logoHTML}Book Appointment
${svcSelectorHTML("appt","Massage","Relax Massage")}
`;
openModal("ov-appt");
}
function prefillApptClient(id){
const c = db.clients.find(c=>c.id===+id); if(!c)return;
eid("appt-name").value = c.name;
eid("appt-cat").value = c.cat;
onCatChange("appt");
if(c.mtype) selectMassage("appt",c.mtype);
eid("appt-price").value = PRICES[c.mtype||c.cat]||300;
}
function updateApptDelFee(){
const zid=+eid("appt-del").value;
const z=db.transportZones.find(x=>x.id===zid)||{fee:0,name:""};
eid("appt-delfee-hint").textContent=z.fee>0?`Delivery fee: ${SAR(z.fee)}`:"";
}
function saveAppt(){
const name = eid("appt-name").value.trim(); if(!name)return;
const cat = eid("appt-cat").value;
const mtype = cat==="Massage" ? getSelectedMassage("appt") : "";
const zid=+eid("appt-del").value;
const z=db.transportZones.find(x=>x.id===zid)||{fee:0,name:"No Delivery"};
db.appts.push({id:Date.now(),client:name,cat,mtype,emp:eid("appt-emp").value,
date:eid("appt-date").value,time:eid("appt-time").value,status:eid("appt-status").value,
price:+eid("appt-price").value||0,delzone:z.name,delfee:z.fee,
discount:+eid("appt-disc").value||0,advance:eid("appt-advance").checked});
closeModal("ov-appt"); refresh();
}
function updateApptStatus(id,status){
const a = db.appts.find(x=>x.id===id); if(!a)return;
if(status==="completed"&&a.status!=="completed"){
db.revenue.push({id:Date.now(),client:a.client,cat:a.cat,mtype:a.mtype,amount:a.price,delfee:a.delfee||0,discount:a.discount||0,date:a.date,method:"Cash",advance:a.advance});
}
a.status=status; refresh();
}
function deleteAppt(id){ db.appts=db.appts.filter(x=>x.id!==id); refresh(); }
// ── MODAL: DAILY INCOME ───────────────────────────────────────────────
function openDailyModal(){
eid("md-daily").innerHTML = `
${logoHTML}Daily Income Entry
${svcSelectorHTML("di","Massage","Relax Massage")}
Profit Preview
Total Amount
− VAT (15%)
− Expenses
= Profit
`;
openModal("ov-daily");
}
function updateProfitPreview(){
const total=+eid("di-total")?.value||0; if(!total){eid("di-preview").style.display="none";return;}
const vat=+eid("di-vat")?.value||0; const exp=+eid("di-exp")?.value||0; const profit=total-vat-exp;
eid("di-preview").style.display="block";
eid("pp-total").textContent=SAR(total); eid("pp-vat").textContent="-"+SAR(vat); eid("pp-exp").textContent="-"+SAR(exp);
eid("pp-profit").textContent=SAR(profit); eid("pp-profit").style.color=profit>=0?"var(--green)":"var(--red)";
}
function saveDailyIncome(){
const total=+eid("di-total").value||0; if(!total)return;
const cat=eid("di-cat").value; const mtype=cat==="Massage"?getSelectedMassage("di"):"";
const zid=+eid("di-del").value;
const z=db.transportZones.find(x=>x.id===zid)||{fee:0,name:"No Delivery"};
const entryId = Date.now();
const entryDate = eid("di-date").value;
// Auto-deduct 1 session worth of stock
const deductions = [];
db.stock.forEach(it=>{
const ppc = it.pcsPerClient||0;
if(ppc<=0) return;
// pieces available in total
const totalPcs = it.totalPkg * it.pkgQty;
const newTotalPcs = Math.max(0, totalPcs - ppc);
const newTotalPkg = newTotalPcs / it.pkgQty;
const actualDeducted = totalPcs - newTotalPcs;
if(actualDeducted>0){
deductions.push({itemId:it.id, itemName:it.name, pcsDeducted:actualDeducted, unit:it.unit, pkgQty:it.pkgQty});
}
db.stock = db.stock.map(s=>s.id===it.id?{...s,totalPkg:newTotalPkg}:s);
});
// Log each stock deduction
if(!db.stockLog) db.stockLog=[];
deductions.forEach(d=>{
db.stockLog.push({
id:Date.now()+Math.random(),
date:entryDate,
itemId:d.itemId,
itemName:d.itemName,
units: d.pcsDeducted/d.pkgQty,
pkgQty:d.pkgQty,
unit:d.unit,
note:`Auto-deducted for session — ${eid("di-client").value||"client"}`,
entryId,
auto:true
});
});
db.dailyIncome.push({
id:entryId,
date:entryDate,
client:eid("di-client").value,
cat,mtype,total,
vat:+eid("di-vat").value||0,
exp:+eid("di-exp").value||0,
delfee:z.fee,
discount:+eid("di-disc").value||0,
method:eid("di-method").value,
advance:eid("di-advance").checked,
note:eid("di-note").value,
stockDeducted: deductions.length
});
closeModal("ov-daily"); refresh();
}
function deleteDailyEntry(id){
// Restore stock when a daily entry is deleted
const entry = db.dailyIncome.find(x=>x.id===id);
if(entry && db.stockLog){
const entryLogs = db.stockLog.filter(l=>l.entryId===id&&l.auto);
entryLogs.forEach(l=>{
const pcsToRestore = l.units * l.pkgQty;
db.stock = db.stock.map(it=>it.id===l.itemId?{...it,totalPkg:it.totalPkg+(pcsToRestore/it.pkgQty)}:it);
});
db.stockLog = db.stockLog.filter(l=>l.entryId!==id||!l.auto);
}
db.dailyIncome=db.dailyIncome.filter(x=>x.id!==id);
refresh();
}
// ── MODAL: REVENUE ────────────────────────────────────────────────────
function openRevModal(){
eid("md-rev").innerHTML = `
${logoHTML}Log Payment
${svcSelectorHTML("rev","Massage","Relax Massage")}
`;
openModal("ov-rev");
}
function saveRevenue(){
const amount=+eid("rev-amount").value||0; if(!amount)return;
const cat=eid("rev-cat").value; const mtype=cat==="Massage"?getSelectedMassage("rev"):"";
const zid=+eid("rev-del").value;
const z=db.transportZones.find(x=>x.id===zid)||{fee:0,name:"No Delivery"};
db.revenue.push({id:Date.now(),client:eid("rev-client").value,cat,mtype,amount,
delfee:z.fee,discount:+eid("rev-disc").value||0,
date:eid("rev-date").value,method:eid("rev-method").value,advance:eid("rev-advance").checked});
closeModal("ov-rev"); refresh();
}
function deleteRev(id){ db.revenue=db.revenue.filter(x=>x.id!==id); refresh(); }
// ── MODAL: CLIENT ─────────────────────────────────────────────────────
function openClientModal(){
eid("md-client").innerHTML = `
${logoHTML}New Client
`;
openModal("ov-client");
}
function saveClient(){
const name=eid("cl-name").value.trim(); if(!name)return;
const cat=eid("cl-cat").value; const mtype=cat==="Massage"?getSelectedMassage("cl"):"";
db.clients.push({id:Date.now(),name,phone:eid("cl-phone").value,cat,mtype,visits:0,defTransport:eid("cl-transport").value,notes:eid("cl-notes").value});
closeModal("ov-client"); refresh();
}
function addVisit(id){ db.clients=db.clients.map(c=>c.id===id?{...c,visits:c.visits+1}:c); refresh(); }
function deleteClient(id){ db.clients=db.clients.filter(x=>x.id!==id); refresh(); }
// ── MODAL: EXPENSE ────────────────────────────────────────────────────
function openExpenseModal(){
eid("md-expense").innerHTML = `
${logoHTML}Add Expense
Category
${EXPENSE_CATS.map(c=>`${c} `).join("")}
Amount (﷼ — excl. VAT)
Note
Date
Assign to Employee
— None — ${EMPLOYEES.map(e=>`${e} `).join("")}
`;
openModal("ov-expense");
}
function saveExpense(){
const amount=+eid("ex-amount").value||0; if(!amount)return;
db.expenses.push({id:Date.now(),cat:eid("ex-cat").value,amount,note:eid("ex-note").value,date:eid("ex-date").value,emp:eid("ex-emp").value});
closeModal("ov-expense"); refresh();
}
function deleteExpense(id){ db.expenses=db.expenses.filter(x=>x.id!==id); refresh(); }
// ── MODAL: SALARY ─────────────────────────────────────────────────────
function openSalaryModal(name,current){
state.salaryTarget=name;
eid("md-salary").innerHTML = `
${logoHTML}Edit Salary
${name}
Monthly Salary (﷼ SAR)
`;
openModal("ov-salary");
}
function saveSalary(){
db.employees=db.employees.map(e=>e.name===state.salaryTarget?{...e,salary:+eid("sal-amount").value||0}:e);
closeModal("ov-salary"); refresh();
}
// ── MODAL: PROMO ──────────────────────────────────────────────────────
function openPromoModal(){
eid("md-promo").innerHTML = `
${logoHTML}New Promotion
Apply to Services
${[...MASSAGE_TYPES,...SERVICE_CATS.filter(s=>s!=="Massage")].map(m=>`
`).join("")}
Applies to all services
`;
// update check display on click
eid("pr-svc-list").querySelectorAll(".svc-opt").forEach(el=>{
el.addEventListener("click",()=>{
const chk=el.querySelector(".svc-check"); const span=chk.querySelector("span");
if(el.classList.contains("sel")){chk.style.background="var(--purple)";chk.style.borderColor="var(--purple)";span.style.display="block";}
else{chk.style.background="transparent";chk.style.borderColor="var(--muted)";span.style.display="none";}
const cnt=eid("pr-svc-list").querySelectorAll(".svc-opt.sel").length;
eid("pr-sel-count").textContent=cnt===0?"Applies to all services":cnt+" service(s) selected";
});
});
openModal("ov-promo");
}
function savePromo(){
const name=eid("pr-name").value.trim(); const value=+eid("pr-value").value; if(!name||!value)return;
const services=[...eid("pr-svc-list").querySelectorAll(".svc-opt.sel")].map(el=>el.dataset.svc);
db.promos.push({id:Date.now(),name,type:eid("pr-type").value,value,services,active:eid("pr-active").checked,expiry:eid("pr-expiry").value,note:eid("pr-note").value});
closeModal("ov-promo"); refresh();
}
function togglePromo(id){ db.promos=db.promos.map(p=>p.id===id?{...p,active:!p.active}:p); refresh(); }
function deletePromo(id){ db.promos=db.promos.filter(x=>x.id!==id); refresh(); }
// ── MODAL: GIFT CARD ──────────────────────────────────────────────────
function openGCModal(){
eid("md-gc").innerHTML = `
${logoHTML}Issue Gift Card
`;
openModal("ov-gc");
}
function updateGCPreview(){
const v=+eid("gc-value")?.value||0; const n=eid("gc-rname")?.value||"—";
if(eid("gc-prev-amount")) eid("gc-prev-amount").textContent = v>0?SAR(v):"—";
if(eid("gc-prev-name")) eid("gc-prev-name").textContent = "For "+n;
}
function saveGiftCard(){
const rname=eid("gc-rname").value.trim(); const value=+eid("gc-value").value; if(!rname||!value)return;
db.giftCards.push({id:Date.now(),code:genCode(),recipientName:rname,recipientPhone:eid("gc-rphone").value,
gifterName:eid("gc-gname").value,value,balance:value,service:eid("gc-svc").value,
issued:eid("gc-issued").value,expiry:eid("gc-expiry").value,note:eid("gc-note").value,redeemed:false});
closeModal("ov-gc"); refresh();
}
function redeemGC(id){
const inp=eid("gc-redeem-"+id); if(!inp)return;
const amount=+inp.value; const g=db.giftCards.find(x=>x.id===id); if(!g||!amount||amount<=0||amount>g.balance)return;
g.balance=Math.max(g.balance-amount,0); if(g.balance===0)g.redeemed=true;
refresh();
}
function deleteGC(id){ db.giftCards=db.giftCards.filter(x=>x.id!==id); refresh(); }
// ── MODAL: REMINDER ───────────────────────────────────────────────────
function openRMModal(){
eid("md-rm").innerHTML = `
${logoHTML}New Reminder
Select Client
— Select or type below —
${db.clients.map(c=>`${c.name} `).join("")}
Client Name
Phone
Reminder Type
${["Appointment Reminder","Follow-up","Birthday","Payment Due","Re-booking","General Note"].map(t=>`${t} `).join("")}
Note
`;
openModal("ov-rm");
}
function prefillRM(id){
const c=db.clients.find(c=>c.id===+id); if(!c)return;
eid("rm-client").value=c.name; eid("rm-phone").value=c.phone;
}
function saveReminder(){
const client=eid("rm-client").value.trim(); if(!client)return;
db.reminders.push({id:Date.now(),client,phone:eid("rm-phone").value,type:eid("rm-type").value,
date:eid("rm-date").value,time:eid("rm-time").value,note:eid("rm-note").value,done:false});
closeModal("ov-rm"); refresh();
}
function toggleReminder(id){ db.reminders=db.reminders.map(r=>r.id===id?{...r,done:!r.done}:r); refresh(); }
function deleteReminder(id){ db.reminders=db.reminders.filter(x=>x.id!==id); refresh(); }
// ── INVOICE ───────────────────────────────────────────────────────────
function openInvoice(clientId){
const c=db.clients.find(x=>x.id===clientId); if(!c)return;
const cR=db.revenue.filter(r=>r.client===c.name);
const sub=cR.length?cR.reduce((s,r)=>s+r.amount,0):(PRICES[c.mtype||c.cat]||300);
const deliv=cR.reduce((s,r)=>s+(r.delfee||0),0);
const disc=cR.reduce((s,r)=>s+(r.discount||0),0);
const vat=sub*VAT; const hasAdv=cR.some(r=>r.advance);
const total=sub+vat+deliv-disc-(hasAdv?ADVANCE:0);
const invN=`PHS-${String(Date.now()).slice(-6)}`;
eid("md-invoice").innerHTML = `
PEARL
HOME SPA
فاتورة ضريبية · Tax Invoice
VAT Reg. No: 300XXXXXXXXX
Invoice
#${invN}
${today}
Bill To / فاتورة إلى
${c.name}
📞 ${c.phone}
${["Service / Type","Date","Qty","Unit Price","Total"].map(h=>`${h} `).join("")}
${cR.length===0?`
${c.mtype||c.cat} ${today}
1
${SAR(sub)}
${SAR(sub)}
`:cR.map(r=>`
${r.mtype||r.cat} ${r.date}
1
${SAR(r.amount)}
${SAR(r.amount)}
`).join("")}
${deliv>0?`Delivery Fee ${SAR(deliv)} `:""}
${disc>0?`Promotion Discount 🎁 -${SAR(disc)} `:""}
${hasAdv?`Advance Deposit Received -${SAR(ADVANCE)} `:""}
Subtotal (excl. VAT) ${SAR(sub)}
VAT 15% · ضريبة القيمة ${SAR(vat)}
${deliv>0?`
Delivery Fee ${SAR(deliv)}
`:""}
${disc>0?`
Discount -${SAR(disc)}
`:""}
${hasAdv?`
Advance Paid -${SAR(ADVANCE)}
`:""}
Total ${SAR(total)}
Print Invoice
Close
`;
openModal("ov-invoice");
}
// ── TRANSPORT FEES TAB ────────────────────────────────────────────────
function renderTransport(){
const html = `
Active Zones ${db.transportZones.length}
${db.transportZones.filter(z=>z.fee>0).length} zones with fees
Fee Range
${SAR(Math.min(...db.transportZones.filter(z=>z.fee>0).map(z=>z.fee).concat([0])))}
–
${SAR(Math.max(...db.transportZones.map(z=>z.fee).concat([0])))}
Min – Max delivery fee
💡 Tip: Click any zone name or fee to edit. New fees apply immediately to all future bookings. Existing appointments keep their original fees.
`;
eid("tab-transport").innerHTML = html;
}
function addTransportZone(){
db.transportZones.push({id:Date.now(),name:"New Zone",fee:0});
renderTransport();
}
function updateZoneName(id,val){ db.transportZones=db.transportZones.map(z=>z.id===id?{...z,name:val}:z); }
function updateZoneFee(id,val){ db.transportZones=db.transportZones.map(z=>z.id===id?{...z,fee:+val||0}:z); }
function deleteZone(id){ db.transportZones=db.transportZones.filter(z=>z.id!==id); renderTransport(); }
// ── STOCK TAB ─────────────────────────────────────────────────────────
function renderStock(){
if(!db.stockLog) db.stockLog=[];
const totalUnits=db.stock.reduce((s,it)=>s+(it.totalPkg*it.pkgQty),0);
const outOf=db.stock.filter(it=>it.totalPkg===0).length;
const totalSessions=db.dailyIncome.length;
// ── Discrepancy analysis ──
// Expected pieces used = sessions × pcsPerClient
// Actual pieces used = from stockLog auto entries
const alerts = db.stock.map(it=>{
const expectedUsed = totalSessions * (it.pcsPerClient||0);
const actualUsed = db.stockLog.filter(l=>l.itemId===it.id&&l.auto).reduce((s,l)=>s+(l.units*l.pkgQty),0);
const diff = actualUsed - expectedUsed; // positive = more used than expected = suspicious
return {...it, expectedUsed, actualUsed, diff};
}).filter(it=>(it.pcsPerClient||0)>0);
const suspicious = alerts.filter(a=>a.diff>0);
const html=`
Total Items ${db.stock.length}
Total Pieces ${Math.floor(totalUnits).toLocaleString()}
Sessions Logged ${totalSessions}
⚠ Discrepancies ${suspicious.length}
${suspicious.length>0?`
⚠️
Stock Discrepancy Detected
${suspicious.length} item(s) show more usage than expected based on ${totalSessions} logged sessions.
This could indicate over-use, theft, or unrecorded sales.
Item
Sessions Logged
Expected Used (${db.stock.find(x=>x.id===suspicious[0]?.id)?.pcsPerClient||"N"} pcs/session)
Actually Used
Extra / Missing ⚠
Equivalent Sessions
${suspicious.map(a=>`
${a.name}
${totalSessions}
${a.expectedUsed.toFixed(1)} pcs
${a.actualUsed.toFixed(1)} pcs
+${a.diff.toFixed(1)} pcs extra
≈ ${(a.pcsPerClient?Math.round(a.diff/a.pcsPerClient):0)} extra sessions worth
`).join("")}
`:
totalSessions>0?`
✅
Stock usage matches ${totalSessions} logged sessions — no discrepancies detected.
`:""}
${totalSessions>0?`
Usage vs Expectation — Per Item
${alerts.map(a=>{
const pct = a.expectedUsed>0 ? Math.min((a.actualUsed/a.expectedUsed)*100,200) : 0;
const over = a.diff>0;
return `
${a.name}
${over?"⚠ Over-used":a.diff===0?"✓ Exact":"Under-used"}
Expected
${a.expectedUsed.toFixed(1)}
pieces
Actual
${a.actualUsed.toFixed(1)}
pieces
Difference
${a.diff>0?"+":""}${a.diff.toFixed(1)}
pieces
${pct.toFixed(0)}% of expected usage · ${a.pcsPerClient} pcs/session setting
`;
}).join("")}
`:""}
Stock Levels & Settings
${db.stockLog.length>0?`
Stock Usage Log
Clear Log
Date Item Pieces Used Source Note
${db.stockLog.slice().reverse().slice(0,20).map(l=>`
${l.date}
${l.itemName}
−${(l.units*l.pkgQty).toFixed(1)} pcs
${l.auto?"📋 Session Auto":"✋ Manual"}
${l.note||"—"}
`).join("")}
`:""}
📋 How the link works:
Every time you log a Daily Income entry (one client session), the platform automatically deducts Pcs Per Session from each item's stock.
If the actual stock used is more than expected based on your session count — the discrepancy panel above will flag it.
This helps you catch over-usage, employee theft, or unrecorded sales immediately.
If you delete a daily entry, the stock is automatically restored.
`;
eid("tab-stock").innerHTML=html;
}
function updateStock(id,field,val){
db.stock=db.stock.map(it=>it.id===id?{...it,[field]:val}:it);
}
function adjustStock(id,delta){
db.stock=db.stock.map(it=>it.id===id?{...it,totalPkg:Math.max(0,it.totalPkg+delta)}:it);
renderStock();
}
function addStockItem(){
db.stock.push({id:Date.now(),name:"New Item",unit:"Pack",pkgQty:1,totalPkg:0,alertAt:0,notes:""});
renderStock();
}
function deleteStockItem(id){
db.stock=db.stock.filter(x=>x.id!==id);
renderStock();
}
function openUseStockModal(id){
const it=db.stock.find(x=>x.id===id); if(!it)return;
const ov=document.createElement("div");
ov.className="ov open";
ov.innerHTML=``;
ov.addEventListener("click",e=>{if(e.target===ov)ov.remove();});
document.body.appendChild(ov);
}
function confirmUseStock(id,pkgQty,unit,itemName){
const qty=+document.getElementById("use-qty")?.value||0;
if(!qty)return;
const note=document.getElementById("use-note")?.value||"";
const date=document.getElementById("use-date")?.value||today;
db.stock=db.stock.map(it=>it.id===id?{...it,totalPkg:Math.max(0,it.totalPkg-qty)}:it);
if(!db.stockLog)db.stockLog=[];
db.stockLog.push({id:Date.now(),date,itemId:id,itemName,units:qty,pkgQty,unit,note});
document.querySelector(".ov.open:last-of-type")?.remove();
renderStock();
}
// ── REFRESH ───────────────────────────────────────────────────────────
function refresh(){
renderHeader();
renderTab(state.tab);
}
// ── SAVE / LOAD / AUTOSAVE ────────────────────────────────────────────
function saveData(){
try {
const payload = JSON.stringify({version:1, savedAt: new Date().toISOString(), db}, null, 2);
const blob = new Blob([payload], {type:"application/json"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
const dateStr = new Date().toISOString().slice(0,10);
a.href = url;
a.download = `PearlHomeSpa_${dateStr}.json`;
a.click();
URL.revokeObjectURL(url);
// Show saved indicator
const ind = eid("save-indicator");
if(ind){ ind.style.display="inline"; setTimeout(()=>ind.style.display="none", 2500); }
} catch(e){ alert("Error saving: " + e.message); }
}
function loadData(event){
const file = event.target.files[0]; if(!file) return;
const reader = new FileReader();
reader.onload = function(e){
try {
const parsed = JSON.parse(e.target.result);
if(!parsed.db) throw new Error("Invalid file — not a Pearl Home Spa backup");
// Confirm before overwriting
if(!confirm(`Load data saved on ${parsed.savedAt ? new Date(parsed.savedAt).toLocaleString() : "unknown date"}?\n\nThis will replace all current data. Make sure you have saved the current data first.`)) return;
// Restore db — merge defaults for any new fields added since last save
Object.keys(parsed.db).forEach(key => { db[key] = parsed.db[key]; });
// Ensure new fields exist
if(!db.stockLog) db.stockLog = [];
if(!db.transportZones) db.transportZones = [{id:1,name:"No Delivery",fee:0}];
db.stock = (db.stock||[]).map(it=>({pcsPerClient:0,alertAt:2,...it}));
refresh();
autoSave();
alert("✅ Data loaded successfully! Your Pearl Home Spa data has been restored.");
} catch(err){ alert("❌ Could not load file: " + err.message); }
};
reader.readAsText(file);
// Reset input so same file can be loaded again if needed
event.target.value = "";
}
// Auto-save to browser localStorage every time data changes
function autoSave(){
try {
localStorage.setItem("pearlhomespa_db", JSON.stringify(db));
localStorage.setItem("pearlhomespa_saved", new Date().toISOString());
} catch(e){ /* localStorage might be full or unavailable */ }
}
function autoLoad(){
try {
const saved = localStorage.getItem("pearlhomespa_db");
if(saved){
const parsed = JSON.parse(saved);
Object.keys(parsed).forEach(key=>{ db[key] = parsed[key]; });
if(!db.stockLog) db.stockLog=[];
if(!db.transportZones) db.transportZones=[{id:1,name:"No Delivery",fee:0}];
db.stock=(db.stock||[]).map(it=>({pcsPerClient:0,alertAt:2,...it}));
console.log("Pearl Home Spa: data auto-loaded from browser storage");
}
} catch(e){ console.log("Auto-load skipped:", e.message); }
}
// Patch refresh to also auto-save
const _origRefresh = refresh;
function refresh(){
_origRefresh();
autoSave();
}
// ── INIT ──────────────────────────────────────────────────────────────
autoLoad(); // ← Restore data from last session automatically
renderNav();
renderHeader();
renderDashboard();