/* eslint-disable */ /* © 2026 K2B NETWORK SOLUTIONS LLC. All rights reserved. Proprietary and confidential. Unauthorized copying, distribution, or use of this file, via any medium, is strictly prohibited. Original work product. */ // JBREWS DMS — Service Department (front of shop). // Scheduling, dispatch board, tech productivity, and RO close — all reading // and writing the shared DMSCore ledger. Closing an RO runs the cascade // (books labor + parts, relieves parts inventory, flags tech hours, creates // the receivable/warranty claim, posts the GL). // RC32 (lane ros): "today" is the STORE's day (the station's local date — never a UTC date, which is tomorrow after 7 pm in Missouri) const svcIsToday = (v) => { if (!v) return false; const s = String(v); if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s === apptToday(); const d = new Date(s); return !isNaN(d) && d.toDateString() === new Date().toDateString(); }; const svcPct = (a, b) => (+b > 0 ? Math.round((+a || 0) / +b * 100) : null); // NaN guard: no hours available → no % const svcNum = (v) => (+v || 0); const ServiceDept = ({ onClose, go }) => { const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => DMS.subscribe(force), []); const [tab, setTab] = React.useState("board"); const [modal, setModal] = React.useState(null); const ros = DMS.repairOrders(); const techs = DMS.technicians(); const appts = DMS.appointments(); const money = (n) => "$" + Number(n || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const act = (r) => window.k2bToast && window.k2bToast(r.msg, r.ok ? "ok" : "warn", 3000); // Open an RO in the RO editor (same pointer Service Reports uses). const openRO = (r) => { try { localStorage.setItem("jb_ro_focus", r.id); } catch (e) {} window.jbROFocus = r.id; go && go("service"); }; // Split-pay ROs close from the editor (payments must add up); legacy ROs close here. // RC20 (fops): the board's Close is the SAME server command as the editor's (POST /api/ro/close); success only from the committed answer const closeOrOpen = async (r) => { const res = await DMS.closeRO(r.id); act(res); if (!res.ok && Array.isArray(r.payments) && res.code === "payments") openRO(r); }; const roTotal = (r) => { const m = DMS.roMoney ? DMS.roMoney(r) : null; return m ? m.total : (r.laborSale || 0) + (r.partsSale || 0) + (r.tax || 0); }; const canClose = !!(DMS.fopsCanCloseRO && DMS.fopsCanCloseRO()); const openROs = ros.filter(r => r.status === "Open"); const closedROs = ros.filter(r => r.status === "Closed"); const techName = (id) => { const t = techs.find(x => x.id === id); return t ? t.name : "— unassigned —"; }; // shop capacity const totalAvail = techs.reduce((s, t) => s + svcNum(t.availToday), 0); const totalFlagged = techs.reduce((s, t) => s + svcNum(t.flaggedToday), 0); // RC32: read-only roles (Technician, Sales, GM) do not get buttons the server refuses them; tech pay rates per T4 const canSvc = !!(DMS.can && DMS.can("service")); const svcCfg = (DMS.svcSettings && DMS.svcSettings()) || {}; const agedDays = (svcCfg.ro && svcCfg.ro.workflow.agedDays) || 7; const seeRates = !(svcCfg.ro && svcCfg.ro.techs.rateViewers === "office+sm") || (DMS.svcCanSetup && DMS.svcCanSetup()) || (DMS._svcRawServiceManager && DMS._svcRawServiceManager()); const doorRate = svcCfg.shopRate != null ? +svcCfg.shopRate : 145; const closedToday = closedROs.filter(r => svcIsToday(r.closed)).length; const stepOf = (r) => (typeof roGroupOf === "function" ? roGroupOf(r) : (r.status === "Closed" ? "closed" : "writeup")); const stepMeta = (g) => (typeof RO_GROUP_META !== "undefined" && RO_GROUP_META[g]) || { dot: "#9AA3AE", label: g }; const stepLabel = (g) => (typeof roGroupLabel === "function" ? roGroupLabel(g) : stepMeta(g).label); const promiseOf = (r) => (typeof roPromiseText === "function" ? roPromiseText(r) : ""); const lateOf = (r) => (typeof roPromiseLate === "function" ? roPromiseLate(r) : false); // booked today = live appointments for today's date (old appointments without a date count as today) const apptTodayStr = apptToday(); const bookedHours = appts.filter(a => (a.status === "Scheduled" || a.status === "Checked In") && (!a.date || a.date === apptTodayStr)).reduce((s, a) => s + (+a.estHours || 0), 0); // sales features: only service roles book / change appointments (Sales and Sales Manager see the list read-only) const canBook = !!(DMS.can && DMS.can("service")); const TABS = [ { k: "board", label: "Dispatch Board" }, { k: "appts", label: `Appointments (${appts.filter(a => a.status === "Scheduled" && (!a.date || a.date >= apptTodayStr)).length})` }, { k: "open", label: `Open ROs (${openROs.length})` }, { k: "techs", label: "Tech Productivity" }, ...(window.ImpRoHistory ? [{ k: "oldros", label: "Old ROs" }] : []), // RC25 (imp): the repair-order history imported from the old system ...(window.ServiceReports ? [{ k: "reports", label: "Reports" }] : []), // RC28 (svc): the service pack — schedules (every service login, techs too), reminders (advisors / the office), Sunbit to collect (the office // and the Service Manager), Setup (+ the Service Manager: the maintenance schedule and the service menu — RC28 fix, svc-aud finding 5) ...(window.SvcMaintLookup && (DMS.svcCanLookup ? DMS.svcCanLookup() : (DMS.svcCanPresent && DMS.svcCanPresent())) ? [{ k: "maint", label: "Maintenance schedules" }] : []), ...(window.SvcReminders && DMS.svcCanRemind && DMS.svcCanRemind() && DMS.svcCanPresent && DMS.svcCanPresent() ? [{ k: "reminders", label: "Reminders" }] : []), ...(window.SvcSunbitCollect && (DMS.svcCanCollect ? DMS.svcCanCollect() : (DMS.svcIsOffice && DMS.svcIsOffice())) ? [{ k: "sunbit", label: "Sunbit to collect" }] : []), ...((DMS.svcCanSetup ? DMS.svcCanSetup() : (DMS.isAdmin && DMS.isAdmin())) || (window.SvcSetupTab && DMS.svcPackCanSetup && DMS.svcPackCanSetup()) ? [{ k: "setup", label: "Setup" }] : []), ]; return (
{/* KPI strip */}
0 ? `${Math.round(bookedHours / totalAvail * 100)}% of capacity` : "no tech hours set"} tone={bookedHours > totalAvail ? "red" : "amber"} /> (Date.now() - new Date(r.opened)) / 86400000 >= agedDays) ? "red" : undefined} />
{canBook && } go && go("service")}>Open RO Editor
{/* Tabs */}
{TABS.map(t => ( ))}
{/* ── DISPATCH BOARD ── */} {tab === "board" && (
{techs.map(t => { const jobs = openROs.filter(r => r.techId === t.id); const prod = svcPct(t.flaggedToday, t.availToday); return (
{t.name}
{t.skill}
= 100 ? "#7CE2A0" : prod >= 80 ? "#F4D27A" : "#F4A09A" }}>{prod == null ? "—" : prod + "%"}
{svcNum(t.flaggedToday)}h / {svcNum(t.availToday)}h
{jobs.length === 0 &&
No active jobs — available
} {jobs.map(r => { const days = Math.floor((Date.now() - new Date(r.opened)) / 86400000); const g = stepOf(r), pr = promiseOf(r), late = lateOf(r); return (
= agedDays ? "#C03546" : r.payType === "Warranty" ? "#B26A00" : r.payType === "Internal" ? "#8050B0" : "#1B66A8") }}>
{r.id} = agedDays ? "#C03546" : "#666" }}>{days}d{days >= agedDays ? " AGED" : ""}
{r.customer}
{stepLabel(g)}{pr ? · ⏰ {pr}{late ? " late" : ""} : null}
{r.payType} · {r.flagHours || 0}h flagged · {money(roTotal(r))}
{canSvc && } {canClose && }
); })}
); })} {/* Unassigned column */} {openROs.filter(r => !techs.find(t => t.id === r.techId)).length > 0 && (
Unassigned
{openROs.filter(r => !techs.find(t => t.id === r.techId)).map(r => (
{r.id} · {r.customer}
{canSvc ? ( ) :
waiting for a technician
}
))}
)}
)} {/* ── APPOINTMENTS (grouped by dealer date; service roles can check in / reschedule / cancel) ── */} {tab === "appts" && setModal({ type: "editAppt", appt: a })} onCancel={(a) => setModal({ type: "cancelAppt", appt: a })} />} {/* ── OPEN ROs ── */} {tab === "open" && ( {openROs.length === 0 && } {openROs.map(r => { const days = Math.floor((Date.now() - new Date(r.opened)) / 86400000); return ( ); })}
RO #Customer / VehicleStepTech Pay TypeFlag HrsLaborPartsTotal OpenAction
No open repair orders. ✓
{r.id} {r.customer}
{r.vehicle}
{stepLabel(stepOf(r))}{promiseOf(r) ? <>
⏰ {promiseOf(r)}{lateOf(r) ? " late" : ""} : null}
{r.tech || "—"} {r.payType} {r.flagHours || 0} {money(r.laborSale)} {money(r.partsSale)} {money(roTotal(r))} = agedDays ? "#C03546" : "#666", fontWeight: days >= agedDays ? 700 : 400 }}>{days}d
{canSvc && } {r.vin && } {canClose && }
)} {/* ── OLD ROs (RC25 imp): the imported repair-order history, looked up on the server ── */} {tab === "oldros" && window.ImpRoHistory && } {/* ── REPORTS (ServiceReports builder) ── */} {tab === "reports" && window.ServiceReports && } {tab === "maint" && window.SvcMaintLookup &&
}{/* RC28 (svc) */} {tab === "reminders" && window.SvcReminders &&
} {tab === "sunbit" && window.SvcSunbitCollect &&
} {/* ── SETUP: standard jobs, labor rates, card fee, recon %, invoice footer ── */} {tab === "setup" && window.ServiceSetupPanel && } {/* ── TECH PRODUCTIVITY ── */} {tab === "techs" && ( <>
Productivity = flagged ÷ available (today). Efficiency = flagged ÷ clock (MTD). Door rate {money(doorRate)}/hr (Service Dept › Setup › Rates).
{seeRates && } {techs.map(t => { const prod = svcPct(t.flaggedToday, t.availToday); const eff = svcPct(t.flaggedMTD, t.clockMTD); return ( {seeRates && } ); })}
TechSkillRateAvail TodayFlagged TodayProductivity Flagged MTDClock MTDEfficiency
{t.name} {t.skill}{money(t.rate)}{svcNum(t.availToday).toFixed(1)} {svcNum(t.flaggedToday).toFixed(1)} = 100 ? "var(--k2b-green)" : prod >= 80 ? "#B26A00" : "#C03546" }}>{prod == null ? "—" : prod + "%"} {svcNum(t.flaggedMTD).toFixed(1)} {svcNum(t.clockMTD).toFixed(1)} = 100 ? "var(--k2b-green)" : eff >= 85 ? "#B26A00" : "#C03546" }}>{eff == null ? "—" : eff + "%"}
)}
{/* Footer */}
ESC-Close {canBook && setModal({ type: "newAppt" })}>F2-Schedule} go && go("service")}>F3-RO Editor go && go("partsdept")}>F4-Parts Dept go && go("accounting")}>F6-Accounting
{modal && modal.type === "flag" && setModal(null)} act={act} />} {modal && modal.type === "newAppt" && canBook && setModal(null)} act={act} />} {modal && modal.type === "editAppt" && canBook && setModal(null)} act={act} />} {modal && modal.type === "cancelAppt" && canBook && setModal(null)} act={act} />}
); }; const FlagHoursDialog = ({ ro, onClose, act }) => { const [h, setH] = React.useState(() => { try { const c = DMS.svcSettings(); return (+(c.ro && c.ro.lists.flagStart != null ? c.ro.lists.flagStart : 1)).toFixed(1); } catch (e) { return "1.0"; } }); // L8 return ( Cancel { act(DMS.flagHoursOnRO(ro.id, parseFloat(h) || 0)); onClose(); }}>Flag Hours }>
{ro.customer} · {ro.tech || "unassigned"}
Currently flagged: {ro.flagHours || 0}h
Add hours: setH(e.target.value.replace(/[^0-9.]/g, ""))} autoComplete="off" name="k2b-flag-hours" style={{ width: 80, ...svcInp }} />
); }; // ── Appointments (sales features, lane 2) ─────────────────────────────────── // Every new appointment carries a date, the customer (picked from the customer file, like the RO // editor's search) and the vehicle (VIN), plus a service type — so the salesperson who sold the car // gets an alert in the DMS when it is booked, and again (same alert) when it is checked in. const apptPad = (n) => String(n).padStart(2, "0"); // today's dealer date as YYYY-MM-DD (the store's PCs run on dealer time) const apptToday = (plusDays) => { const d = new Date(); if (plusDays) d.setDate(d.getDate() + plusDays); return d.getFullYear() + "-" + apptPad(d.getMonth() + 1) + "-" + apptPad(d.getDate()); }; // "2026-10-12" -> "Mon Oct 12" from the parts (never new Date("YYYY-MM-DD"), which is the day before in Missouri) const apptFmtDay = (day) => { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(day || "")); if (!m) return ""; return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])).toLocaleDateString("en-US", { timeZone: "UTC", weekday: "short", month: "short", day: "numeric" }).replace(/,/g, ""); }; const apptFmtTime = (hhmm) => { const m = /^(\d{1,2}):(\d{2})/.exec(String(hhmm || "")); if (!m) return hhmm || ""; const h = +m[1]; return ((h % 12) || 12) + ":" + m[2] + (h < 12 ? " AM" : " PM"); }; // RC32 (lane ros): the store's lists — appointment types (L10), advisors from the user list (L1), transport (R7); missing = today's const apptSvcCfg = () => { try { return DMS.svcSettings ? DMS.svcSettings() : null; } catch (e) { return null; } }; const apptTypes = () => { const c = apptSvcCfg(); const l = c && c.ro && c.ro.lists.apptTypes; return (Array.isArray(l) && l.length ? l : null) || DMS.SVC_APPT_TYPES || ["Oil change", "Maintenance", "Tires & alignment", "Brakes", "Battery", "Inspection", "Diagnosis", "Warranty / recall", "Repair", "Other"]; }; const apptAdvisors = () => { try { if (DMS.svcAdvisors) { const a = DMS.svcAdvisors({}); if (a && a.names && a.names.length) return [a.pick].concat(a.names.filter(n => n !== a.pick)); } } catch (e) {} return (typeof SVC_ADVISORS !== "undefined" && Array.isArray(SVC_ADVISORS) ? SVC_ADVISORS : ["Mark Service"]); }; const apptTransports = (cur) => { const c = apptSvcCfg(); const l = (c && c.ro && Array.isArray(c.ro.lists.transport) && c.ro.lists.transport.length ? c.ro.lists.transport : ["Drop-off", "Waiter", "Loaner", "Shuttle"]); return cur && !l.includes(cur) ? l.concat([cur]) : l; }; const apptVehLabel = (v) => [v.yr, v.mk, v.mdl].filter(Boolean).join(" ") || v.label || ""; const ApptDialog = ({ techs, appt, onClose, act }) => { const editing = !!appt; const [f, setF] = React.useState(() => ({ date: (appt && appt.date) || apptToday(), time: (appt && appt.time) || "08:00", serviceType: (appt && appt.serviceType) || "", concern: (appt && appt.concern) || "", advisor: (appt && appt.advisor) || apptAdvisors()[0], techId: (appt && appt.techId) || "", estHours: String((appt && appt.estHours) || "1.0"), transport: (appt && appt.transport) || "Drop-off", })); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const [q, setQ] = React.useState(""); const [pick, setPick] = React.useState(null); // {custNum, name, phone, email, city, vehicles} const [walkIn, setWalkIn] = React.useState(false); const [walkName, setWalkName] = React.useState(""); const [vehSel, setVehSel] = React.useState("__other"); const [vin, setVin] = React.useState(""); const [vehDesc, setVehDesc] = React.useState(""); const [msg, setMsg] = React.useState(""); // "Other vehicle": a 17-character VIN typed / pasted here fills the vehicle description ("2022 CHEVROLET SILVERADO 1500") from our server // (window.DMS.vin: MarketCheck -> NHTSA -> the VIN itself) when the description is still blank. A typed description stays; Undo puts it back. const descTyped = React.useRef(new Set()); const vinFill = (typeof VlUseVinFill === "function") ? VlUseVinFill({ vin, enabled: !editing && vehSel === "__other", keys: { vehDesc: "ymm" }, read: () => ({ vehDesc }), write: (p) => { if (p.vehDesc !== undefined) setVehDesc(p.vehDesc); }, typed: () => descTyped.current, }) : null; const rows = React.useMemo(() => (q.trim().length >= 2 && DMS.customerSearch ? DMS.customerSearch(q).slice(0, 30) : []), [q]); const choose = (c) => { setPick(c); setWalkIn(false); setVehSel(c.vehicles && c.vehicles.length ? "0" : "__other"); setVin(""); setVehDesc(""); descTyped.current = new Set(); setMsg(""); }; const save = () => { if (!/^\d{4}-\d{2}-\d{2}$/.test(f.date)) { setMsg("Pick a date."); return; } if (!f.serviceType) { setMsg("Pick a service type."); return; } if (editing) { const r = DMS.updateAppointment(appt.id, { date: f.date, time: f.time || "08:00", serviceType: f.serviceType, concern: f.concern, advisor: f.advisor, techId: f.techId, estHours: f.estHours, transport: f.transport }); if (!r.ok) { setMsg(r.msg); return; } act(r); onClose(); return; } let cust = null, customer = ""; if (pick) { cust = { custNum: pick.custNum, name: pick.name, phone: pick.phone || "", email: pick.email || "" }; customer = pick.name; } else if (walkIn && walkName.trim()) customer = walkName.trim().toUpperCase(); else { setMsg("Pick the customer, or tick walk-in and type a name."); return; } let veh = null, vehicleText = ""; if (pick && vehSel !== "__other" && pick.vehicles[+vehSel]) { const v = pick.vehicles[+vehSel]; veh = { vin: v.vin || "", yr: v.yr || "", mk: v.mk || "", mdl: v.mdl || "", miles: v.miles || "" }; vehicleText = apptVehLabel(v); } else if (vin.trim() || vehDesc.trim()) { const desc = vehDesc.trim().toUpperCase(); const m = desc.match(/^(\d{4})\s+(\S+)\s*(.*)$/); veh = { vin: vin.trim().toUpperCase().replace(/[\s-]/g, ""), yr: m ? m[1] : "", mk: m ? m[2] : "", mdl: m ? m[3] : desc, miles: "" }; vehicleText = desc; } const r = DMS.addAppointment({ date: f.date, time: f.time || "08:00", custNum: cust ? cust.custNum : "", cust, customer, veh, vin: veh ? veh.vin : "", vehicle: vehicleText, serviceType: f.serviceType, concern: f.concern, advisor: f.advisor, techId: f.techId, estHours: f.estHours, transport: f.transport }); if (!r.ok) { setMsg(r.msg); return; } act(r); onClose(); }; const lbl = { fontSize: 11, fontWeight: 700, color: "#555" }; return ( Cancel }>
{editing ? (
{appt.customer || appt.custNum || "—"}{appt.vehicle ? " · " + appt.vehicle : ""}{appt.vin ? · {appt.vin} : null}
) : (
Customer * — name, phone, email or customer # {pick ? (
{pick.name}{pick.phone}{pick.city} ) : ( <> { setQ(e.target.value); setMsg(""); }} placeholder="e.g. SMITH · 573-555-0101 · 5735550101" style={{ ...svcInp, marginTop: 3 }} /> {!walkIn && q.trim().length >= 2 && (
{rows.length === 0 &&
No customer matches. Tick walk-in below, or add the customer in the CRM first.
} {rows.map(c => (
choose(c)} style={{ display: "flex", gap: 10, padding: "6px 8px", cursor: "pointer", borderBottom: "1px solid #EEE", fontSize: 12.5 }}> {c.name}{c.phone}{c.city} {c.vehicles.length} veh
))}
)} {walkIn && setWalkName(e.target.value)} placeholder="LAST, FIRST" style={{ ...svcInp, marginTop: 4 }} />} )}
)} {!editing && (pick || walkIn) && (
Vehicle {vehSel === "__other" && ( <>
setVin(e.target.value.toUpperCase())} onBlur={() => vinFill && vinFill.onBlur()} placeholder="VIN (if known)" spellCheck={false} style={{ ...svcInp, fontFamily: "var(--jb-mono)" }} /> { descTyped.current.add("vehDesc"); setVehDesc(e.target.value); }} placeholder="2020 CHEVROLET MALIBU" style={svcInp} />
{vinFill && typeof VlVinNote === "function" && } )}
)}
{msg &&
{msg}
}
); }; const ApptCancelDialog = ({ appt, onClose, act }) => { const [reason, setReason] = React.useState(""); return ( Keep it }>
{appt.customer} · {appt.serviceType || "Service"} · {apptFmtDay(appt.date)} {apptFmtTime(appt.time)}
setReason(e.target.value)} placeholder="Why? (optional — e.g. customer called, rebooking)" style={svcInp} />
); }; const ApptList = ({ appts, techs, act, canEdit, onReschedule, onCancel }) => { const today = apptToday(), tomorrow = apptToday(1); const dated = appts.filter(a => a && /^\d{4}-\d{2}-\d{2}$/.test(String(a.date || ""))); const noDate = appts.filter(a => a && !/^\d{4}-\d{2}-\d{2}$/.test(String(a.date || ""))); const byWhen = (a, b) => String(a.date).localeCompare(String(b.date)) || String(a.time || "").localeCompare(String(b.time || "")); const groups = [ ["today", "Today — " + apptFmtDay(today), dated.filter(a => a.date === today).sort(byWhen)], ["tomorrow", "Tomorrow — " + apptFmtDay(tomorrow), dated.filter(a => a.date === tomorrow).sort(byWhen)], ["later", "Later", dated.filter(a => a.date > tomorrow).sort(byWhen)], ["past", "Past", dated.filter(a => a.date < today).sort(byWhen).reverse()], ]; const tone = (st) => st === "Checked In" ? "var(--k2b-green)" : st === "Cancelled" ? "#8a8f98" : st === "Done" ? "#0E7A37" : "#B26A00"; const techName = (id) => { const t = techs.find(x => x.id === id); return t ? t.name : "—"; }; const row = (a) => { const live = a.status !== "Checked In" && a.status !== "Cancelled" && a.status !== "Done"; return ( {a.date ? apptFmtDay(a.date) : "—"}
{apptFmtTime(a.time)} {a.serviceType || "—"} {a.customer}{a.custNum ? #{a.custNum} : a.date ? walk-in : null}
{a.vehicle}{a.vin ? " · " + a.vin : ""} {a.concern} {canEdit && live ? ( ) : techName(a.techId)} {(+a.estHours || 0).toFixed(1)} {a.transport} {a.status} {a.roId ? {a.roId} : null} {canEdit && live && (
{a.date ? : null}
)} ); }; const head = ( WhenTypeCustomer / VehicleConcern TechEst HrsTransportStatus{canEdit ? "Action" : "RO"} ); return (
{!canEdit &&
View only — the service team books, checks in and changes appointments.
} {appts.length === 0 &&
No appointments scheduled.
} {groups.map(([k, label, list]) => list.length === 0 ? null : (
{label} ({list.length})
{head}{list.map(row)}
))} {noDate.length > 0 && (
No date (made before the update) ({noDate.length})
These were booked before appointments had a date. {canEdit ? "Set a date so the right salesperson is told." : ""}
{head}{noDate.map(row)}
)}
); }; const SvcKpi = ({ label, value, sub, tone }) => { const border = tone === "red" ? "#C03546" : tone === "amber" ? "#D9920A" : tone === "ok" ? "var(--jb-green, #16A34A)" : "var(--jb-border, #C0C0C0)"; const color = tone === "red" ? "#C03546" : tone === "amber" ? "#9A6700" : tone === "ok" ? "var(--jb-green, #16A34A)" : "var(--jb-navy, #11337A)"; return (
{label}
{value}
{sub &&
{sub}
}
); }; const svcInp = { background: "var(--k2b-yellow, #FFFFE1)", border: "1px solid #808080", padding: "3px 6px", fontFamily: "var(--font-ui)", fontSize: 13, width: "100%", boxSizing: "border-box" }; const svcTable = { width: "100%", borderCollapse: "collapse", fontFamily: "'Courier Prime', monospace", fontSize: 12 }; const svcHeadRow = { background: "#C0C0C0", color: "var(--k2b-maroon, #8a1c1c)", borderBottom: "1px solid #808080", position: "sticky", top: 0 }; const sTh = { padding: "3px 6px", textAlign: "left", whiteSpace: "nowrap" }; const sThL = { ...sTh }; const sThR = { ...sTh, textAlign: "right" }; const sThC = { ...sTh, textAlign: "center" }; const sTd = { padding: "3px 6px", color: "var(--k2b-navy, #11337A)", verticalAlign: "top" }; const sTdMono = { ...sTd, fontWeight: 700, color: "var(--k2b-blue-data-dk, #11337A)" }; const sTdR = { ...sTd, textAlign: "right" }; const sTdC = { ...sTd, textAlign: "center" }; Object.assign(window, { ServiceDept, ApptDialog, ApptList, ApptCancelDialog, apptFmtDay, apptToday });