/* 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 — RC32 (lane ros): the technician's phone screen "My jobs" (owner Q2: view only now; letting techs update their own
// jobs is decided later — T5). A technician signing in on a PHONE (T2, on by default) lands here; on a laptop the technician gets the
// RO screen (view only). Nothing here writes: no buttons that change an RO, no prices or costs (a technician never sees cost).
// • My jobs: Mine · Unassigned · All open, counters (to do, working, flagged today, done), cards sorted by promise time.
// • One job per screen: the vehicle + VIN (Copy VIN), complaint / cause / correction / tech notes / parts as text, the inspection.
// • Bottom bar: Service menu (the same presentation, read for the customer) · Print · Full RO (the RO screen, view only).
// My jobs and the RO editor are never on the page together (exactly one data-svc-menu-btn and one data-tech-viewonly).
// Script order: after ServiceCore.jsx.txt (DMS.svc* helpers), before app.jsx (app.jsx asks svcTechPhoneMode() for the landing route).
// The ONE predicate for "a technician on a phone with My jobs turned on" — app.jsx (view-only bar, landing) and ServiceTickets use it.
const svcTechPhoneMode = () => {
try {
const D = window.DMS;
if (!D || !D.roleIs || !D.roleIs("Technician")) return false;
const s = D.svcSettings ? D.svcSettings() : null;
if (!s || !s.ro || !s.ro.techs || !s.ro.techs.myJobsOnPhone) return false;
return (D.svcDeviceType ? D.svcDeviceType() : "laptop") === "phone";
} catch (e) { return false; }
};
const TJ_STATUS_TONE = { ACTIVE: { fg: "#1456A0", bg: "#E5EFFA", bar: "#1B66A8" }, PEND: { fg: "#8A5A00", bg: "#FFF3D9", bar: "#C8862A" }, HOLD: { fg: "#B23A2E", bg: "#FBE9E7", bar: "#B23A2E" }, DONE: { fg: "#0E7A37", bg: "#E6F7EC", bar: "#16A34A" } };
const TJ_ORDER = { ACTIVE: 0, PEND: 1, HOLD: 2, DONE: 3 };
const tjBtn = { minHeight: 48, minWidth: 48, borderRadius: 10, border: "1px solid var(--jb-border-strong, #C3CAD6)", background: "var(--jb-surface, #fff)", color: "var(--jb-text)", fontSize: 15, fontWeight: 700, cursor: "pointer", fontFamily: "var(--jb-font)", padding: "0 12px" };
const tjWord = (cfg, code) => { const w = cfg && cfg.ro && cfg.ro.lists && cfg.ro.lists.jobStatus; return (w && w[code]) || code; };
const TechJobs = ({ ros, techs, cfg, go, onClose, who, onFullRo }) => {
const [, force] = React.useReducer(n => n + 1, 0);
React.useEffect(() => DMS.subscribe(force), []);
const me = who || (typeof roMeIds === "function" ? roMeIds() : { techIds: [] });
const linked = me.techIds.length > 0;
const [scope, setScope0] = React.useState(linked ? "mine" : "all");
// RC33 (part C, RC32 acceptance F1): on a fresh browser the technicians arrive AFTER the first draw — a linked sign-in still opens on "Mine"
// (once the link is known), unless the technician already picked a list
const picked = React.useRef(false);
const setScope = (k) => { picked.current = true; setScope0(k); };
React.useEffect(() => { if (linked && !picked.current) setScope0("mine"); }, [linked]);
const [find, setFind] = React.useState(false);
const [q, setQ] = React.useState("");
const [open, setOpen] = React.useState(null); // { roId, jobId }
const [insp, setInsp] = React.useState(false);
const live = [];
(ros || []).filter(r => r.status === "Open").forEach(r => (r.ops || []).forEach((o, i) => { if (o && o.approval !== "declined") live.push({ ro: r, job: o, n: i + 1 }); }));
const mine = live.filter(x => me.techIds.includes(x.job.techId));
const unassigned = live.filter(x => !x.job.techId);
const base = scope === "mine" ? mine : scope === "unassigned" ? unassigned : live;
const ql = q.trim().toLowerCase();
const hit = (x) => !ql || [x.ro.id, x.ro.customer, x.ro.vehicle, x.ro.vin, x.job.desc, x.job.op].join(" ").toLowerCase().includes(ql)
|| (window.DMS && DMS.custQueryHit ? DMS.custQueryHit(q, x.ro.custNum, [x.ro.cust && x.ro.cust.phone]) : false);
const promiseT = (r) => { const d = r.promise ? new Date(r.promise).getTime() : NaN; return isNaN(d) ? Infinity : d; };
const list = base.filter(hit).sort((a, b) => promiseT(a.ro) - promiseT(b.ro) || (TJ_ORDER[a.job.status || "PEND"] - TJ_ORDER[b.job.status || "PEND"]) || String(a.ro.id).localeCompare(String(b.ro.id)) || a.n - b.n);
const counts = { todo: mine.filter(x => (x.job.status || "PEND") === "PEND" || x.job.status === "HOLD").length, working: mine.filter(x => x.job.status === "ACTIVE").length, done: mine.filter(x => x.job.status === "DONE").length };
const myTech = (techs || []).find(t => me.techIds.includes(t.id));
const showHours = !!(cfg && cfg.ro && cfg.ro.techs.showOwnHours) && !!myTech;
const viewOnly = 👁 View only;
const topBar = (left, title, right) => (
{left}
{title}
{right}
);
const darkBtn = { ...tjBtn, background: "transparent", color: "#fff", border: "1px solid rgba(255,255,255,.35)" };
// ── one job per screen ──
const cur = open ? live.find(x => x.ro.id === open.roId && String(x.job.id) === String(open.jobId)) : null;
if (open && !cur) { setTimeout(() => setOpen(null), 0); }
if (cur) {
const r = cur.ro, j = cur.job;
const tone = TJ_STATUS_TONE[j.status || "PEND"] || TJ_STATUS_TONE.PEND;
const parts = (r.partsLines || []).filter(l => l.jobId != null && String(l.jobId) === String(j.id) && +l.qty > 0);
const items = (cfg && cfg.ro && cfg.ro.lists.inspection.items) || (typeof RO_MPI_ITEMS !== "undefined" ? RO_MPI_ITEMS : []);
const gr = (cfg && cfg.ro && cfg.ro.lists.inspection.grades) || ["OK", "Attn", "Now"];
const mpi = r.mpi || {};
const nG = items.filter(i => mpi[i] === "g").length, nY = items.filter(i => mpi[i] === "y").length, nR = items.filter(i => mpi[i] === "r").length;
const veh = r.veh || {};
const vehTitle = [veh.yr, veh.mk, veh.mdl].filter(Boolean).join(" ") || r.vehicle || "";
const copyVin = () => { try { navigator.clipboard.writeText(r.vin || veh.vin || ""); window.k2bToast && window.k2bToast("VIN copied.", "ok", 1800); } catch (e) {} };
const box = (label, text, extra) => (
{label}{extra}
{text || "Not written yet."}
);
return (
{topBar(
, insp ? `Inspection · ${r.id}` : `${r.id} · Job ${cur.n}`, <>{viewOnly}{window.JBHelperButton ?
: null}>)}
{insp ? (
<>
{nG + nY + nR} of {items.length} checked · {nR} {gr[2]} · {nY} {gr[1]}
{items.map(it => {
const v = mpi[it];
const c = v === "r" ? ["#B23A2E", gr[2]] : v === "y" ? ["#C8862A", gr[1]] : v === "g" ? ["#16A34A", gr[0]] : ["#C7CDD4", "—"];
return (
{it}
{c[1]}
);
})}
{r.mpiNotes ? box("Inspection notes", r.mpiNotes) : null}
>
) : (
<>
{vehTitle}
VIN {r.vin || veh.vin || "—"}
{(r.vin || veh.vin) && }
{(r.miles || veh.miles) ? {Number(r.miles || veh.miles).toLocaleString()} mi : null}
{typeof roPromiseText === "function" && roPromiseText(r) ? ⏰ {roPromiseText(r)} : null}
{r.transport ? {r.transport} : null}
● {tjWord(cfg, j.status || "PEND")}{j.status === "HOLD" && j.holdReason ? " · " + j.holdReason : ""}
{j.desc || j.op} {j.op}
{box("Complaint", j.complaint)}
{box("Cause", j.cause)}
{box("Correction", j.correction)}
{box("Tech notes", j.techNotes,
never prints)}
Parts on this job
{parts.length === 0 &&
None.
}
{parts.map(l =>
{l.desc || l.part} × {l.qty} {l.part}
)}
>
)}
{window.SvcMenuButton ? : }
);
}
// ── My jobs ──
const seg = (k, label, n) => (
);
return (
{topBar(typeof window.k2bToggleAppMenu === "function" ?
: null, "My jobs",
<>{viewOnly}{window.JBHelperButton ?
: null}
>)}
{[["to do", counts.todo], ["working", counts.working], ...(showHours ? [["flagged today", (+myTech.flaggedToday || 0).toFixed(1) + " h"]] : []), ["done", counts.done]].map(([l, v], i) => (
))}
{!linked &&
Your sign-in is not linked to a technician yet, so "Mine" is empty — ask the office (Service Dept › Setup › Technicians).
}
{seg("mine", "Mine", mine.length)}{seg("unassigned", "Unassigned", unassigned.length)}{seg("all", "All open", live.length)}
{find &&
}
{list.length === 0 && (q ?
setQ("")} /> : {scope === "mine" ? "No jobs for you right now." : "No jobs here."}
)}
{list.map(x => {
const st = x.job.status || "PEND", tone = TJ_STATUS_TONE[st] || TJ_STATUS_TONE.PEND;
const veh = String(x.ro.vehicle || "").replace(/^(\d{4})\s+(CHEVROLET|CHEVY|GMC|BUICK|CADILLAC)\s+/i, "$1 ");
const pr = typeof roPromiseText === "function" ? roPromiseText(x.ro) : "";
const late = typeof roPromiseLate === "function" ? roPromiseLate(x.ro) : false;
return (
);
})}
Sorted by promise time. Tap a job to open it. View only — your service advisor types the status, cause, correction and inspection.
);
};
Object.assign(window, { TechJobs, svcTechPhoneMode });