99 lines
4.8 KiB
Markdown
99 lines
4.8 KiB
Markdown
# 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:**
|
||
1. Add `{ requestKey: null }` to all PocketBase mutating calls in `pocketbase.js`:
|
||
```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 });
|
||
```
|
||
2. Add double-submit guard: disable button on click, re-enable in success/error paths.
|
||
```js
|
||
const submitBtn = document.getElementById('submit-create-ro');
|
||
if (submitBtn && submitBtn.disabled) return;
|
||
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Creating...'; }
|
||
```
|
||
3. 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.js` onAuthStateChanged → `else` branch
|
||
- `repair-orders.js` onAuthStateChanged → `else` branch
|
||
- `appointments.js` auth check → `else` branch
|
||
|
||
```js
|
||
} 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:
|
||
```bash
|
||
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:
|
||
```js
|
||
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:
|
||
```js
|
||
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).
|