Production Board — Local Only

No install required. Data is saved in this browser only. Booting…

Recent Sales Orders

Recent Customers

// above your loader block. // firebase.appCheck().activate('YOUR_RECAPTCHA_SITE_KEY', true); // 3) Firestore handles const db = firebase.firestore(); const UNITS_COL = db.collection("units"); const MOVES_COL = db.collection("moves"); const META_RECENT_SO_DOC = db.collection("meta").doc("recent_sos"); const META_RECENT_CUST_DOC = db.collection("meta").doc("recent_customers"); // 4) Mirror keys & helpers (same names as your current file) const STORAGE_KEY = "prod_board_v3"; const RECENT_SO_KEY = "recent_sos_v1"; const RECENT_CUSTOMERS_KEY = "recent_customers_v1"; const VALID_AREAS = [ "Unbuilt (Staging)","Cabinet Assembly","Cabinet Assembly WIP","Top Coil","Top Coil WIP", "Cabinet Braze","Cabinet Braze WIP","Leak Test","Leak Test WIP","Loom Install","Loom Install WIP", "Stage Wire","Wired on Vac","Charge Station","Charged in WIP","Test Bay","Tested in WIP","Final Assembly","Shipping" ]; const DEFAULT_AREA = "Unbuilt (Staging)"; const normalizeArea = a => VALID_AREAS.includes(a) ? a : DEFAULT_AREA; const _cache = Object.create(null); const emptyStateJSON = JSON.stringify({ units: [], moves: [] }); _cache[STORAGE_KEY] = emptyStateJSON; _cache[RECENT_SO_KEY] = "[]"; _cache[RECENT_CUSTOMERS_KEY] = "[]"; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); const chunk = (arr, n = 450) => { const out=[]; for(let i=0;i { const data = d.data() || {}; if (!VALID_AREAS.includes(data.CurrentArea)) data.CurrentArea = DEFAULT_AREA; return { id: d.id, ...data }; }); const moves = mSnap.docs.map(d => ({ ...d.data() })); _cache[STORAGE_KEY] = JSON.stringify({ units, moves }); } async function rebuildRecentsCacheFromFirestore() { const [soDoc, custDoc] = await Promise.all([META_RECENT_SO_DOC.get(), META_RECENT_CUST_DOC.get()]); _cache[RECENT_SO_KEY] = JSON.stringify(soDoc.exists ? (soDoc.data().list || []) : []); _cache[RECENT_CUSTOMERS_KEY] = JSON.stringify(custDoc.exists ? (custDoc.data().list || []) : []); } // 6) Push current cache into UI state and re-render function applyCacheToUI() { try { const parsed = JSON.parse(_cache[STORAGE_KEY] || emptyStateJSON); window.state = window.state || { units: [], moves: [] }; window.state.units = parsed.units || []; window.state.moves = parsed.moves || []; if (window.renderBoard) window.renderBoard(); if (window.renderLog) window.renderLog(); } catch (e) { console.error(e); } } // 7) Write-through helpers (keep your monkey-patch behavior) async function syncStateToFirestore(stateObj) { const targetUnits = Array.isArray(stateObj.units) ? stateObj.units : []; const targetMoves = Array.isArray(stateObj.moves) ? stateObj.moves : []; // Upsert units const existing = await UNITS_COL.get(); const existingIds = new Set(existing.docs.map(d => d.id)); const targetIds = new Set(); const upsertOps = []; for (const u of targetUnits) { const id = u && u.id ? String(u.id) : (Math.random().toString(36).slice(2)); targetIds.add(id); const { id: _omit, ...fields } = (u || {}); fields.CurrentArea = normalizeArea(fields.CurrentArea); upsertOps.push({ type:"set", ref: UNITS_COL.doc(id), data: fields }); } for (const id of existingIds) { if (!targetIds.has(id)) upsertOps.push({ type:"del", ref: UNITS_COL.doc(id) }); } for (const group of chunk(upsertOps, 450)) { const batch = db.batch(); for (const op of group) { if (op.type === "set") batch.set(op.ref, op.data); else batch.delete(op.ref); } await batch.commit(); } // Replace moves const oldMoves = await MOVES_COL.get(); for (const group of chunk(oldMoves.docs, 450)) { const batch = db.batch(); group.forEach(d => batch.delete(d.ref)); await batch.commit(); } const moveOps = targetMoves.map((m, i) => ({ ref: MOVES_COL.doc(moveDocId(m,i)), data: m })); for (const group of chunk(moveOps, 450)) { const batch = db.batch(); group.forEach(({ref, data}) => batch.set(ref, data)); await batch.commit(); } } async function syncRecentsToFirestore(key, arr) { if (key === RECENT_SO_KEY) await META_RECENT_SO_DOC.set({ list: arr }, { merge: true }); if (key === RECENT_CUSTOMERS_KEY) await META_RECENT_CUST_DOC.set({ list: arr }, { merge: true }); } // 8) Monkey‑patch localStorage for the 3 keys (write-through to Firestore) const _orig = { getItem: localStorage.getItem.bind(localStorage), setItem: localStorage.setItem.bind(localStorage), removeItem: localStorage.removeItem.bind(localStorage), clear: localStorage.clear?.bind(localStorage) }; const _isManaged = (key) => (key === STORAGE_KEY || key === RECENT_SO_KEY || key === RECENT_CUSTOMERS_KEY); localStorage.getItem = function(key) { if (_isManaged(key)) return _cache[key] ?? (key === STORAGE_KEY ? emptyStateJSON : "[]"); return _orig.getItem(key); }; localStorage.setItem = function(key, value) { if (_isManaged(key)) { try { _cache[key] = (value ?? (key === STORAGE_KEY ? emptyStateJSON : "[]")); if (key === STORAGE_KEY) { const stateObj = JSON.parse(_cache[key] || emptyStateJSON); syncStateToFirestore(stateObj).catch(console.error); } else { const arr = JSON.parse(_cache[key] || "[]"); syncRecentsToFirestore(key, Array.isArray(arr) ? arr : []).catch(console.error); } } catch (e) { console.error(e); } return; } return _orig.setItem(key, value); }; localStorage.removeItem = function(key) { if (_isManaged(key)) { if (key === STORAGE_KEY) { _cache[key] = emptyStateJSON; (async () => { const [uSnap, mSnap] = await Promise.all([UNITS_COL.get(), MOVES_COL.get()]); for (const group of chunk(uSnap.docs, 450)) { const b=db.batch(); group.forEach(d=>b.delete(d.ref)); await b.commit(); } for (const group of chunk(mSnap.docs, 450)) { const b=db.batch(); group.forEach(d=>b.delete(d.ref)); await b.commit(); } })().catch(console.error); } else { _cache[key] = "[]"; (async () => { if (key === RECENT_SO_KEY) await META_RECENT_SO_DOC.set({ list: [] }); if (key === RECENT_CUSTOMERS_KEY) await META_RECENT_CUST_DOC.set({ list: [] }); })().catch(console.error); } return; } return _orig.removeItem(key); }; localStorage.clear = function() { (async () => { try { _cache[STORAGE_KEY] = emptyStateJSON; _cache[RECENT_SO_KEY] = "[]"; _cache[RECENT_CUSTOMERS_KEY]= "[]"; const [uSnap, mSnap] = await Promise.all([UNITS_COL.get(), MOVES_COL.get()]); for (const group of chunk(uSnap.docs, 450)) { const b=db.batch(); group.forEach(d=>b.delete(d.ref)); await b.commit(); } for (const group of chunk(mSnap.docs, 450)) { const b=db.batch(); group.forEach(d=>b.delete(d.ref)); await b.commit(); } await Promise.all([ META_RECENT_SO_DOC.set({list:[]}), META_RECENT_CUST_DOC.set({list:[]}) ]); } catch(e){ console.error(e); } })(); }; // 9) HYDRATE once, then keep live with throttled re-render (async () => { try { await rebuildStateCacheFromFirestore(); await rebuildRecentsCacheFromFirestore(); // Wait for your UI functions to be ready for (let i = 0; i < 200; i++) { if (window.renderBoard && window.renderLog) break; await sleep(15); } // First render from Firestore applyCacheToUI(); setStatus('Firebase live'); // Throttled refresh after any snapshot let throttle; const requestRerender = () => { if (throttle) return; throttle = setTimeout(() => { throttle = null; applyCacheToUI(); }, 150); }; UNITS_COL.onSnapshot(async () => { await rebuildStateCacheFromFirestore(); requestRerender(); }); MOVES_COL.onSnapshot(async () => { await rebuildStateCacheFromFirestore(); requestRerender(); }); META_RECENT_SO_DOC.onSnapshot(doc => { _cache[RECENT_SO_KEY] = JSON.stringify(doc.exists ? (doc.data().list || []) : []); }); META_RECENT_CUST_DOC.onSnapshot(doc => { _cache[RECENT_CUSTOMERS_KEY] = JSON.stringify(doc.exists ? (doc.data().list || []) : []); }); } catch (e) { console.error("Firestore bridge init error:", e); setStatus('Firestore bridge error'); } })(); } catch (err) { console.error('Bridge fatal error', err); setStatus('Bridge fatal error'); } };