4.8 KiB
ShopProQuote Troubleshooting Patterns
Pitfall: PocketBase SDK auto-cancellation (requestKey: null)
PocketBase JS SDK has auto-cancellation enabled by default. All create() and update() calls to the same collection share a request key. A second call cancels the first — user sees "auto-cancelled" error.
Symptom: Creating a repair order shows "The request was auto-cancelled."
Root cause: Rapid double-submit fires two addDoc calls. Second POST cancels first via PocketBase's requestKey dedup.
Fix:
- Add
{ requestKey: null }to all PocketBase mutating calls inpocketbase.js:// addDoc const record = await pb.collection(collName).create(pbData, { requestKey: null }); // updateDoc const record = await pb.collection(collName).update(docId, pbData, { requestKey: null }); // setDoc (both create and update branches) return pb.collection(collName).update(records[0].id, pbData, { requestKey: null }); return pb.collection(collName).create(pbData, { requestKey: null }); - Add double-submit guard: disable button on click, re-enable in success/error paths.
const submitBtn = document.getElementById('submit-create-ro'); if (submitBtn && submitBtn.disabled) return; if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Creating...'; } - Re-enable on success AND error.
Pitfall: Auth redirect loop (stale PocketBase token)
Symptom: Page constantly refreshes between index.html → dashboard.html → index.html.
Root cause: index.html line 131: if(localStorage.getItem('pocketbase_auth')){location.replace('dashboard.html')}. This is a blind truthy check — ANY string in localStorage (including expired tokens) triggers the redirect. When dashboard.js bounces back to index.html (invalid auth), the stale token was NEVER cleared, so index.html redirects again. Infinite loop.
Fix: Add localStorage.removeItem('pocketbase_auth') in ALL auth-fail redirect paths BEFORE navigating away:
dashboard.jsonAuthStateChanged →elsebranchrepair-orders.jsonAuthStateChanged →elsebranchappointments.jsauth check →elsebranch
} else {
localStorage.removeItem('pocketbase_auth');
window.location.href = 'index.html';
}
Caveat: If the user sees login.html in the redirect, that's browser cache — old JS files referenced login.html which no longer exists. All live files redirect to index.html. Hard-refresh (Ctrl+Shift+R) or clear site data.
Pitfall: ro.status vs ro.workStatus confusion
Symptom: Dashboard "Completed Today" shows 0 even when ROs were completed. History filters don't work.
Root cause: Repair orders have TWO status fields:
ro.status— customer service status:'waiter'or'drop-off'ro.workStatus— work progress:'diagnosing','in-progress','waiting-for-approval','waiting-for-parts','waiting-for-pickup','completed'
Dashboard.js had 5 instances checking ro.status === 'completed' (never true — customer service status is never 'completed'). Must check ro.workStatus.
Also: For completed filters, use completedTime or financial.completedAt for the date, NOT writeupTime. A RO written last week and completed today should count as completed today.
Pitfall: DuckDNS auto-detects home IP, not VPS IP
Symptom: grajmedia.duckdns.org resolves to 162.81.173.34 (home) instead of 51.81.84.34 (VPS).
Root cause: DuckDNS update script at /opt/duckdns/update.sh uses &ip= (empty) which makes DuckDNS use the requesting server's public IP — the home server, not the VPS.
Fix: Explicitly set VPS IP:
echo url="https://www.duckdns.org/update?domains=grajmedia&token=<token>&ip=51.81.84.34" | curl -k -s -o /opt/duckdns/duck.log -K -
The /etc/hosts entry 127.0.0.1 grajmedia.duckdns.org is intentional — allows local access without hairpin NAT round-trip through the VPS.
Pitfall: Completion handler scoping (const in try block)
Symptom: Financial modal "Complete" button does nothing after entering all data.
Root cause: roData declared with const inside a try block, but referenced outside it:
try {
const roData = getROData(roId); // block-scoped
...
} catch (e) {}
if (roData) { ... } // ReferenceError — roData not defined here
The error crashes handleFinancialCompletion() silently.
Fix: Move declaration outside the try block:
const roData = repairOrders.find(ro => ro.id === roId);
try {
const servicesLines = roData?.services ? ...;
Pattern: Status modal with segmented control
The work status modal uses a 2×3 grid of buttons instead of a dropdown. Each status has a color-coded pill with active state highlighting. Clicking a status immediately applies it (except 'completed' which opens financial modal, and leaving 'waiting-for-parts' which shows a confirmation).