Picker Scan
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
<title>LeveLux Order Scan</title>
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Picker Scan">
<meta name="theme-color" content="#16140f">
<meta name="robots" content="noindex, nofollow, noarchive">
<style>
:root {
--gold: #c9a24b;
--gold-dark: #a8842f;
--black: #16140f;
--bg: #f7f5f0;
--card: #ffffff;
--text: #1c1c1c;
--muted: #74705f;
--good: #2e7d32;
--bad: #b3261e;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
body { min-height: 100vh; display: flex; flex-direction: column; }
header { background: var(--black); color: #fff; padding: 18px 20px; display: flex; align-items: center; gap: 12px; }
header .logo { font-weight: 700; letter-spacing: 0.08em; color: var(--gold); font-size: 18px; text-transform: uppercase; }
header .sub { font-size: 12px; color: #cfc7ae; margin-top: 2px; }
main { flex: 1; padding: 20px; max-width: 640px; margin: 0 auto; width: 100%; }
/* Passcode gate */
#gate { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; padding: 40px 20px; text-align: center; }
#gate h1 { font-size: 22px; margin-bottom: 6px; }
#gate p { color: var(--muted); margin-bottom: 20px; }
#gate input { font-size: 22px; letter-spacing: 4px; text-align: center; padding: 14px; width: 100%; max-width: 260px;
border: 2px solid #ddd; border-radius: 10px; margin-bottom: 14px; }
#gate button, .btn { background: var(--gold); color: var(--black); border: none; border-radius: 10px;
font-size: 16px; font-weight: 700; padding: 14px 22px; cursor: pointer; }
#gate button:active, .btn:active { background: var(--gold-dark); }
.btn:disabled { background: #ddd; color: #999; cursor: not-allowed; }
#gateError { color: var(--bad); font-size: 14px; min-height: 18px; }
[hidden] { display: none !important; }
/* Login (name + PIN) */
#loginBar { display: flex; flex-wrap: wrap; align-items: center; background: var(--card);
border-radius: 12px; padding: 14px 16px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); gap: 10px; }
#loginBar .who { font-size: 14px; color: var(--muted); white-space: nowrap; }
#nameSelect { flex: 1 1 160px; font-size: 16px; font-weight: 700; padding: 10px 12px; border-radius: 8px; border: 2px solid #ddd;
background: #fafafa; }
#pinInput { flex: 0 1 100px; font-size: 16px; padding: 10px 12px; border-radius: 8px; border: 2px solid #ddd; text-align: center; letter-spacing: 2px; }
#loginBtn { flex: 0 0 auto; padding: 10px 18px; font-size: 15px; }
#loginError { color: var(--bad); font-size: 13px; min-height: 16px; margin-bottom: 8px; }
/* Logged-in state / pace */
#loggedInBar { background: var(--card); border-radius: 12px; padding: 12px 16px; margin-bottom: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; }
.whoLoggedIn { font-size: 14px; color: var(--muted); }
.whoLoggedIn b { color: var(--text); }
.linkBtn { background: none; border: none; color: var(--gold-dark); font-weight: 700; font-size: 13px;
text-decoration: underline; cursor: pointer; padding: 0; margin-left: 8px; }
.pace { font-size: 13px; color: var(--muted); font-weight: 600; }
.paceWarning { background: var(--bad); color: #fff; border-radius: 10px; padding: 10px 14px; font-size: 14px;
font-weight: 700; margin-bottom: 16px; text-align: center; }
/* Tabs */
.tabBar { display: flex; gap: 8px; margin-bottom: 16px; }
.tabBtn { flex: 1; padding: 10px; border-radius: 10px; border: 2px solid #ddd; background: #fff;
font-weight: 700; font-size: 14px; cursor: pointer; color: var(--text); }
.tabBtn.active { background: var(--black); color: var(--gold); border-color: var(--black); }
.tabBadge { display: inline-block; background: var(--bad); color: #fff; border-radius: 999px;
padding: 1px 8px; font-size: 12px; margin-left: 6px; }
/* Problem Rack */
.problemCard { background: var(--card); border-radius: 12px; padding: 14px 16px; margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.problemCard .pmeta { font-size: 13px; color: var(--muted); margin-bottom: 10px; }
.problemCard .pmeta b { color: var(--text); }
.resolveBtn { width: 100%; padding: 12px; font-size: 15px; }
#problemEmpty { text-align: center; color: var(--muted); padding: 30px 20px; }
.flagBadge { display: inline-block; background: var(--bad); color: #fff; font-weight: 700; font-size: 12px;
border-radius: 999px; padding: 3px 10px; margin-left: 8px; }
#skipBtn { width: 100%; margin-top: 8px; background: #fff; border: 2px solid var(--bad); color: var(--bad);
font-weight: 700; border-radius: 10px; padding: 12px; cursor: pointer; font-size: 15px; }
#skipBtn:active { background: #fbeceb; }
/* Scan box */
#scanBox { background: var(--card); border-radius: 14px; padding: 18px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); margin-bottom: 18px; }
#scanBox label { font-size: 13px; color: var(--muted); display: block; margin-bottom: 8px; }
#scanInput { width: 100%; font-size: 22px; padding: 16px; border: 2px solid var(--gold); border-radius: 10px; text-align: center; }
#scanInput:focus { outline: none; box-shadow: 0 0 0 3px rgba(201,162,75,0.35); }
#scanHint { text-align: center; color: var(--muted); font-size: 13px; margin-top: 20px; }
/* Result card */
#result { display: none; }
#result.show { display: block; }
.orderCard { background: var(--card); border-radius: 14px; padding: 18px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.orderCard h2 { margin: 0 0 4px 0; font-size: 20px; }
.orderMeta { color: var(--muted); font-size: 14px; margin-bottom: 14px; }
.orderMeta b { color: var(--text); }
.itemsCountBadge { display: inline-block; background: var(--black); color: var(--gold); font-weight: 700;
border-radius: 999px; padding: 4px 12px; font-size: 13px; margin-left: 6px; }
.itemsCountBadge.mismatch { background: var(--bad); color: #fff; }
.item { display: flex; gap: 12px; padding: 12px 0; border-top: 1px solid #eee; }
.item img { width: 72px; height: 72px; object-fit: cover; border-radius: 8px; background: #eee; flex-shrink: 0; }
.item .noPhoto { width: 72px; height: 72px; border-radius: 8px; background: #eee; display: flex; align-items: center;
justify-content: center; color: #aaa; font-size: 11px; text-align: center; flex-shrink: 0; }
.item .desc { font-size: 14px; font-weight: 600; }
.item .subline { font-size: 13px; color: var(--muted); margin-top: 2px; }
.verifyBar { margin-top: 16px; padding-top: 16px; border-top: 2px solid #eee; }
.verifiedBadge { display: flex; align-items: center; gap: 10px; background: #eaf5ea; color: var(--good);
border-radius: 10px; padding: 12px 14px; font-size: 14px; font-weight: 600; }
.verifiedBadge.no { background: #fbeceb; color: var(--bad); }
.photoCheck { display: flex; align-items: center; gap: 8px; font-size: 14px; margin-top: 12px; color: var(--text); }
.photoCheck input { width: 20px; height: 20px; }
#verifyBtn { width: 100%; font-size: 18px; padding: 16px; margin-top: 10px; }
#notFound { text-align: center; padding: 40px 20px; color: var(--bad); }
#notFound .tn { color: var(--muted); font-family: monospace; margin-top: 6px; }
#loading { text-align: center; padding: 30px; color: var(--muted); }
footer { text-align: center; color: var(--muted); font-size: 12px; padding: 16px; }
</style>
</head>
<body>
<div id="gate">
<div class="logo" style="color:var(--black); font-size:22px;">LEVELUX</div>
<h1>Order Scan</h1>
<p>Enter the shop passcode to continue</p>
<input id="gateInput" type="password" inputmode="numeric" placeholder="Passcode" autocomplete="off">
<div id="gateError"></div>
<button id="gateBtn" class="btn">Enter</button>
</div>
<div id="app" hidden style="display:flex; flex-direction:column; flex:1;">
<header>
<div>
<div class="logo">LEVELUX</div>
<div class="sub">Picker Scan</div>
</div>
</header>
<main>
<div id="loginBar">
<label class="who" for="nameSelect">I am</label>
<select id="nameSelect"></select>
<input id="pinInput" type="password" inputmode="numeric" placeholder="PIN" maxlength="6" autocomplete="off">
<button id="loginBtn" class="btn">Log in</button>
</div>
<div id="loginError"></div>
<div id="loggedInBar" hidden>
<div class="whoLoggedIn">Picking as <b id="loggedInName"></b><button id="switchUserBtn" class="linkBtn">switch</button></div>
<div id="paceDisplay" class="pace"></div>
</div>
<div id="paceWarning" class="paceWarning" hidden></div>
<div class="tabBar" id="tabBar" hidden>
<button class="tabBtn active" id="tabScanBtn">Scan</button>
<button class="tabBtn" id="tabProblemBtn">Problem Rack<span class="tabBadge" id="problemBadge" hidden>0</span></button>
</div>
<div id="scanView">
<div id="scanBox">
<label for="scanInput">Scan or type a tracking number</label>
<input id="scanInput" type="text" inputmode="text" autocomplete="off" placeholder="Tracking number">
</div>
<div id="loading" hidden>Looking up order…</div>
<div id="notFound" hidden>
<div style="font-size:40px;">✕</div>
<div>No order found for</div>
<div class="tn" id="notFoundTn"></div>
</div>
<div id="result">
<div class="orderCard">
<h2 id="ordBuyer">—</h2>
<div class="orderMeta">
Tracking: <b id="ordTn"></b><br>
Items required: <b id="ordCount"></b><span id="mismatchBadge" class="itemsCountBadge" hidden></span>
<span id="problemFlagBadge" class="flagBadge" hidden>🚩 Problem Rack</span>
</div>
<div id="itemsList"></div>
<div class="verifyBar">
<div id="verifiedStatus"></div>
<label class="photoCheck" id="photoCheckRow" hidden>
<input type="checkbox" id="photoCheckbox"> I checked this matches the photo(s)
</label>
<button id="verifyBtn" class="btn" hidden disabled>Mark Verified</button>
<button id="skipBtn" hidden>Can't find it — Problem Rack</button>
</div>
</div>
</div>
<div id="scanHint">Scan the next order any time — no need to clear this one first.</div>
</div>
<div id="problemView" hidden>
<div id="problemList"></div>
<div id="problemEmpty" hidden>Nothing in the Problem Rack right now.</div>
</div>
</main>
<footer>LeveLux · Internal use only</footer>
</div>
<script>
(function(){
// ---- CONFIG ----
var AIRTABLE_TOKEN = "patvgfKRgkTjGpkwQ.0712b5f7904923d0b32046f25d9ac23767d16546628bbcb4ba3fcf7ecb37c07a";
var BASE_ID = "apppTgdA1jhJ26E6R";
var TABLE_ID = "tblhZBUPpmAtt3eue"; // Order Photos
var PASSCODE = "8425";
var STAFF_NAMES = ["Carolina Leyva","Jason Long","Megan Wilson","Vanessa Ramirez-Salazar","Mariel","Other"];
var STAFF_PINS = {
"Carolina Leyva": "1234",
"Jason Long": "2345",
"Megan Wilson": "3456",
"Vanessa Ramirez-Salazar": "4567",
"Mariel": "5678",
"Other": "0000"
};
var PACE_TARGET_PER_HOUR = 100; // picking target — packing tool uses 55
var PACE_GRACE_MINUTES = 10; // don't flag pace until this many minutes into the login session
var API_BASE = "https://api.airtable.com/v0/" + BASE_ID + "/" + TABLE_ID;
function authHeaders(){
return {"Authorization": "Bearer " + AIRTABLE_TOKEN, "Content-Type": "application/json"};
}
// ---- Gate (shop-wide passcode) ----
var gate = document.getElementById('gate');
var app = document.getElementById('app');
var gateInput = document.getElementById('gateInput');
var gateBtn = document.getElementById('gateBtn');
var gateError = document.getElementById('gateError');
function tryUnlock(){
if (gateInput.value === PASSCODE) {
sessionStorage.setItem('llx_unlocked', '1');
gate.hidden = true;
app.hidden = false;
initApp();
} else {
gateError.textContent = "That's not it — try again.";
gateInput.value = '';
gateInput.focus();
}
}
gateBtn.addEventListener('click', tryUnlock);
gateInput.addEventListener('keydown', function(e){ if (e.key === 'Enter') tryUnlock(); });
try {
if (sessionStorage.getItem('llx_unlocked') === '1') {
gate.hidden = true;
app.hidden = false;
initApp();
}
} catch(e) {}
// ---- Login (name + personal PIN) ----
var nameSelect = document.getElementById('nameSelect');
var pinInput = document.getElementById('pinInput');
var loginBtn = document.getElementById('loginBtn');
var loginError = document.getElementById('loginError');
var loginBar = document.getElementById('loginBar');
var loggedInBar = document.getElementById('loggedInBar');
var loggedInName = document.getElementById('loggedInName');
var switchUserBtn = document.getElementById('switchUserBtn');
var paceDisplay = document.getElementById('paceDisplay');
var paceWarning = document.getElementById('paceWarning');
var currentUser = null;
var loginTime = null;
function todayKey(){
var d = new Date();
return d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate();
}
function logKey(name){ return 'llx_pickLog_' + name + '_' + todayKey(); }
function getLog(name){
try { return JSON.parse(localStorage.getItem(logKey(name)) || '[]'); } catch(e){ return []; }
}
function addLogEntry(name){
var log = getLog(name);
log.push(Date.now());
try { localStorage.setItem(logKey(name), JSON.stringify(log)); } catch(e){}
return log;
}
function renderNameSelect(){
nameSelect.innerHTML = '';
var placeholder = document.createElement('option');
placeholder.value = '';
placeholder.textContent = 'Select your name…';
nameSelect.appendChild(placeholder);
STAFF_NAMES.forEach(function(n){
var o = document.createElement('option');
o.value = n;
o.textContent = n;
nameSelect.appendChild(o);
});
}
function doLogin(){
var name = nameSelect.value;
var pin = pinInput.value.trim();
if (!name) { loginError.textContent = 'Select your name first.'; return; }
if (!pin || pin !== STAFF_PINS[name]) {
loginError.textContent = "That PIN doesn't match — try again.";
pinInput.value = '';
pinInput.focus();
return;
}
loginError.textContent = '';
currentUser = name;
loginTime = Date.now();
try {
sessionStorage.setItem('llx_current_user', name);
sessionStorage.setItem('llx_login_time', String(loginTime));
} catch(e){}
showLoggedIn();
}
function showLoggedIn(){
loginBar.hidden = true;
loginError.textContent = '';
loggedInBar.hidden = false;
loggedInName.textContent = currentUser;
pinInput.value = '';
tabBar.hidden = false;
updatePaceDisplay();
refreshProblemBadge();
if (scanInput) scanInput.focus();
}
function doLogout(){
currentUser = null;
loginTime = null;
try {
sessionStorage.removeItem('llx_current_user');
sessionStorage.removeItem('llx_login_time');
} catch(e){}
loggedInBar.hidden = true;
paceWarning.hidden = true;
tabBar.hidden = true;
loginBar.hidden = false;
nameSelect.value = '';
pinInput.value = '';
showTab('scan');
nameSelect.focus();
}
loginBtn.addEventListener('click', doLogin);
pinInput.addEventListener('keydown', function(e){ if (e.key === 'Enter') doLogin(); });
switchUserBtn.addEventListener('click', doLogout);
function computeRate(name){
var log = getLog(name);
var now = Date.now();
var oneHourAgo = now - 60*60*1000;
return log.filter(function(t){ return t >= oneHourAgo; }).length;
}
function updatePaceDisplay(){
if (!currentUser) return;
var rate = computeRate(currentUser);
var minutesLoggedIn = loginTime ? (Date.now() - loginTime) / 60000 : 999;
paceDisplay.textContent = 'Last hour: ' + rate + ' picked · target ' + PACE_TARGET_PER_HOUR + '/hr';
if (minutesLoggedIn >= PACE_GRACE_MINUTES && rate < PACE_TARGET_PER_HOUR) {
paceWarning.hidden = false;
paceWarning.textContent = '⚠️ Pace check — you\'re at ' + rate + '/hr, target is ' + PACE_TARGET_PER_HOUR + '/hr. Pick up the pace!';
} else {
paceWarning.hidden = true;
}
}
setInterval(function(){ if (currentUser) updatePaceDisplay(); }, 30000);
try {
var savedUser = sessionStorage.getItem('llx_current_user');
var savedTime = sessionStorage.getItem('llx_login_time');
if (savedUser && STAFF_NAMES.indexOf(savedUser) !== -1) {
currentUser = savedUser;
loginTime = savedTime ? parseInt(savedTime, 10) : Date.now();
}
} catch(e){}
// ---- App init ----
var scanInput, itemsList, resultEl, notFoundEl, notFoundTn, loadingEl;
var ordBuyer, ordTn, ordCount, mismatchBadge, problemFlagBadge, verifiedStatus, verifyBtn, skipBtn, photoCheckRow, photoCheckbox;
var tabBar, tabScanBtn, tabProblemBtn, problemBadge, scanView, problemView, problemList, problemEmpty;
var currentOrderRecords = []; // raw airtable records for the currently displayed order
function initApp(){
scanInput = document.getElementById('scanInput');
itemsList = document.getElementById('itemsList');
resultEl = document.getElementById('result');
notFoundEl = document.getElementById('notFound');
notFoundTn = document.getElementById('notFoundTn');
loadingEl = document.getElementById('loading');
ordBuyer = document.getElementById('ordBuyer');
ordTn = document.getElementById('ordTn');
ordCount = document.getElementById('ordCount');
mismatchBadge = document.getElementById('mismatchBadge');
problemFlagBadge = document.getElementById('problemFlagBadge');
verifiedStatus = document.getElementById('verifiedStatus');
verifyBtn = document.getElementById('verifyBtn');
skipBtn = document.getElementById('skipBtn');
photoCheckRow = document.getElementById('photoCheckRow');
photoCheckbox = document.getElementById('photoCheckbox');
tabBar = document.getElementById('tabBar');
tabScanBtn = document.getElementById('tabScanBtn');
tabProblemBtn = document.getElementById('tabProblemBtn');
problemBadge = document.getElementById('problemBadge');
scanView = document.getElementById('scanView');
problemView = document.getElementById('problemView');
problemList = document.getElementById('problemList');
problemEmpty = document.getElementById('problemEmpty');
renderNameSelect();
if (currentUser) {
showLoggedIn();
} else {
loginBar.hidden = false;
}
scanInput.addEventListener('keydown', function(e){
if (e.key === 'Enter') {
e.preventDefault();
var v = scanInput.value.trim();
if (v) lookupOrder(v);
}
});
photoCheckbox.addEventListener('change', function(){
verifyBtn.disabled = !photoCheckbox.checked;
});
verifyBtn.addEventListener('click', markVerified);
skipBtn.addEventListener('click', markSkipped);
tabScanBtn.addEventListener('click', function(){ showTab('scan'); });
tabProblemBtn.addEventListener('click', function(){ showTab('problem'); });
}
function showTab(which){
if (which === 'problem') {
tabScanBtn.classList.remove('active');
tabProblemBtn.classList.add('active');
scanView.hidden = true;
problemView.hidden = false;
loadProblemRack();
} else {
tabProblemBtn.classList.remove('active');
tabScanBtn.classList.add('active');
problemView.hidden = true;
scanView.hidden = false;
}
}
function refreshProblemBadge(){
var formula = "{Problem Rack}=1";
var url = API_BASE + "?filterByFormula=" + encodeURIComponent(formula) + "&fields%5B%5D=Tracking%20Number";
fetch(url, {headers: authHeaders()})
.then(function(r){ return r.json(); })
.then(function(data){
var recs = data.records || [];
var uniq = {};
recs.forEach(function(r){ uniq[r.fields['Tracking Number']] = true; });
var n = Object.keys(uniq).length;
if (n > 0) { problemBadge.hidden = false; problemBadge.textContent = n; }
else { problemBadge.hidden = true; }
})
.catch(function(){ /* silent — badge just won't update */ });
}
function esc(s){ return String(s).replace(/'/g, "\\'"); }
function lookupOrder(tn){
resultEl.classList.remove('show');
notFoundEl.hidden = true;
loadingEl.hidden = false;
var formula = "AND({Tracking Number}='" + esc(tn) + "')";
var url = API_BASE + "?filterByFormula=" + encodeURIComponent(formula);
fetch(url, {headers: authHeaders()})
.then(function(r){ return r.json(); })
.then(function(data){
loadingEl.hidden = true;
if (!data.records || data.records.length === 0) {
notFoundTn.textContent = tn;
notFoundEl.hidden = false;
return;
}
showOrder(tn, data.records);
})
.catch(function(err){
loadingEl.hidden = true;
notFoundTn.textContent = tn + " (network error — try again)";
notFoundEl.hidden = false;
});
}
function showOrder(tn, records){
currentOrderRecords = records.slice().sort(function(a,b){
return (a.fields['Item #']||0) - (b.fields['Item #']||0);
});
var first = currentOrderRecords[0].fields;
ordBuyer.textContent = first['Buyer'] || 'Unknown buyer';
ordTn.textContent = tn;
var required = first['Order Item Count'];
ordCount.textContent = (required != null ? required : '—');
if (required != null && required !== currentOrderRecords.length) {
mismatchBadge.hidden = false;
mismatchBadge.classList.add('mismatch');
mismatchBadge.textContent = currentOrderRecords.length + " on file";
} else {
mismatchBadge.hidden = true;
}
itemsList.innerHTML = '';
currentOrderRecords.forEach(function(r){
var f = r.fields;
var div = document.createElement('div');
div.className = 'item';
var photo = (f['Photo'] && f['Photo'][0]) ? f['Photo'][0].url : null;
var imgHtml = photo
? '<img src="' + photo + '" alt="item photo">'
: '<div class="noPhoto">No photo</div>';
div.innerHTML = imgHtml +
'<div><div class="desc">' + (f['Description']||'Item ' + (f['Item #']||'')) + '</div>' +
'<div class="subline">' + (f['Brand'] ? f['Brand'] + ' · ' : '') + '$' + (f['Price']!=null?f['Price']:'0') + '</div></div>';
itemsList.appendChild(div);
});
var isVerified = currentOrderRecords.some(function(r){ return !!r.fields['Verified']; });
var packerName = '';
currentOrderRecords.forEach(function(r){ if (r.fields['Packer']) packerName = r.fields['Packer']; });
var isProblem = currentOrderRecords.some(function(r){ return !!r.fields['Problem Rack']; });
problemFlagBadge.hidden = !isProblem;
photoCheckbox.checked = false;
verifyBtn.disabled = true;
if (isVerified) {
verifiedStatus.innerHTML = '<div class="verifiedBadge">✓ Verified by ' + (packerName || 'someone') + '</div>';
verifyBtn.hidden = true;
photoCheckRow.hidden = true;
skipBtn.hidden = true;
} else {
verifiedStatus.innerHTML = '<div class="verifiedBadge no">Not yet verified</div>';
photoCheckRow.hidden = !currentUser;
verifyBtn.hidden = false;
verifyBtn.textContent = currentUser ? ('Confirm Picked — ' + currentUser) : 'Log in above first';
if (!currentUser) verifyBtn.disabled = true;
skipBtn.hidden = !currentUser;
}
resultEl.classList.add('show');
// ready for next scan immediately
scanInput.value = '';
scanInput.focus();
}
function markVerified(){
var name = currentUser;
if (!name) {
nameSelect.focus();
return;
}
verifyBtn.disabled = true;
verifyBtn.textContent = 'Saving…';
var updates = currentOrderRecords.map(function(r){
return { id: r.id, fields: { "Packer": name, "Verified": true } };
});
// Airtable allows up to 10 records per PATCH call
var batches = [];
for (var i = 0; i < updates.length; i += 10) batches.push(updates.slice(i, i+10));
function runBatch(idx){
if (idx >= batches.length) {
verifiedStatus.innerHTML = '<div class="verifiedBadge">✓ Verified by ' + name + '</div>';
verifyBtn.hidden = true;
photoCheckRow.hidden = true;
skipBtn.hidden = true;
addLogEntry(name);
updatePaceDisplay();
return;
}
fetch(API_BASE, {
method: 'PATCH',
headers: authHeaders(),
body: JSON.stringify({records: batches[idx]})
}).then(function(r){ return r.json(); })
.then(function(){ runBatch(idx+1); })
.catch(function(){
verifyBtn.disabled = false;
verifyBtn.textContent = 'Save failed — tap to retry';
});
}
runBatch(0);
}
function markSkipped(){
var name = currentUser;
if (!name) { nameSelect.focus(); return; }
skipBtn.disabled = true;
skipBtn.textContent = 'Saving…';
var updates = currentOrderRecords.map(function(r){
return { id: r.id, fields: { "Problem Rack": true, "Problem Flagged By": name } };
});
var batches = [];
for (var i = 0; i < updates.length; i += 10) batches.push(updates.slice(i, i+10));
function runBatch(idx){
if (idx >= batches.length) {
skipBtn.disabled = false;
skipBtn.textContent = "Can't find it — Problem Rack";
refreshProblemBadge();
// move straight to the next scan
resultEl.classList.remove('show');
scanInput.value = '';
scanInput.focus();
return;
}
fetch(API_BASE, {
method: 'PATCH',
headers: authHeaders(),
body: JSON.stringify({records: batches[idx]})
}).then(function(r){ return r.json(); })
.then(function(){ runBatch(idx+1); })
.catch(function(){
skipBtn.disabled = false;
skipBtn.textContent = 'Save failed — tap to retry';
});
}
runBatch(0);
}
// ---- Problem Rack (built into the same tool — any logged-in staff can resolve) ----
function loadProblemRack(){
problemList.innerHTML = '';
problemEmpty.hidden = true;
var formula = "{Problem Rack}=1";
var url = API_BASE + "?filterByFormula=" + encodeURIComponent(formula);
fetch(url, {headers: authHeaders()})
.then(function(r){ return r.json(); })
.then(function(data){
var recs = data.records || [];
if (recs.length === 0) {
problemEmpty.hidden = false;
problemBadge.hidden = true;
return;
}
// group by tracking number
var groups = {};
recs.forEach(function(r){
var tn = r.fields['Tracking Number'] || '(no tracking #)';
if (!groups[tn]) groups[tn] = [];
groups[tn].push(r);
});
var tns = Object.keys(groups);
problemBadge.hidden = false;
problemBadge.textContent = tns.length;
tns.forEach(function(tn){
var group = groups[tn];
var f = group[0].fields;
var flaggedBy = '';
group.forEach(function(r){ if (r.fields['Problem Flagged By']) flaggedBy = r.fields['Problem Flagged By']; });
var div = document.createElement('div');
div.className = 'problemCard';
div.innerHTML =
'<div class="pmeta">' +
'<b>' + (f['Buyer'] || 'Unknown buyer') + '</b><br>' +
'Tracking: <b>' + tn + '</b> · ' + group.length + ' item(s)<br>' +
'Flagged by: <b>' + (flaggedBy || 'unknown') + '</b>' +
'</div>' +
'<button class="btn resolveBtn">Found it — Resolve</button>';
var btn = div.querySelector('.resolveBtn');
btn.addEventListener('click', function(){ resolveProblem(tn, group, btn); });
problemList.appendChild(div);
});
})
.catch(function(){
problemEmpty.hidden = false;
problemEmpty.textContent = 'Could not load the Problem Rack — check your connection and try again.';
});
}
function resolveProblem(tn, group, btn){
var name = currentUser;
btn.disabled = true;
btn.textContent = 'Saving…';
var updates = group.map(function(r){
return { id: r.id, fields: { "Problem Rack": false, "Problem Resolved By": name } };
});
var batches = [];
for (var i = 0; i < updates.length; i += 10) batches.push(updates.slice(i, i+10));
function runBatch(idx){
if (idx >= batches.length) {
loadProblemRack();
return;
}
fetch(API_BASE, {
method: 'PATCH',
headers: authHeaders(),
body: JSON.stringify({records: batches[idx]})
}).then(function(r){ return r.json(); })
.then(function(){ runBatch(idx+1); })
.catch(function(){
btn.disabled = false;
btn.textContent = 'Save failed — tap to retry';
});
}
runBatch(0);
}
})();
</script>
</body>
</html>
