Files
brandize-barcode/agent.md
T
2026-06-10 06:44:34 -03:00

204 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Health Routine Package Scanner
> ALWAYS UPDATE THIS FILE AFTER MAKING CHANGES
## What this is
A single-page web app (`index.html`) used internally by Health Routine staff to scan **package** barcodes.
A user selects their name, scans/types a code, and the result is looked up via a webhook and appended to a table.
Runs in any browser — designed for phones, tablets, and desktop.
## Files
```
index.html — the entire app (HTML + CSS + JS, no build step)
logo.png — Health Routine brand logo (used in the header)
agent.md — this file
start — bash script that launches live-server on localhost:8080
```
## Design
Colors are pulled directly from healthroutine.de's CSS variables:
| Token | Hex | Usage |
|------------------|-----------|-------------------------------------|
| `--primary` | `#005f5a` | Controls bar, table header, buttons |
| `--primary-dark` | `#004845` | Hover states |
| `--primary-light`| `#e0f0ef` | Row hover, badge backgrounds |
| `--bg` | `#f7f7fa` | Page background |
| `--card` | `#eaeaf2` | Alternating table rows |
| `--sage` | `#82917d` | (brand footer green, available) |
| `--error` | `#c71b1b` | Error states |
| `--success` | `#15803d` | Success toasts |
Font: system font stack (`-apple-system`, `Segoe UI`, etc.)
## Webhook
**Base URL (scan):** `https://brandize.app.n8n.cloud/webhook/transfer`
**Base URL (batches):** `https://brandize.app.n8n.cloud/webhook/batch`
### GET — load users
Called once on page load to populate the user dropdown.
```
GET <base_url>
Authorization: Basic <base64>
```
**Actual response shape** (confirmed via console):
```json
{ "Name": ["Oliver", "Luka", "Benni"] }
```
`buildDropdown()` handles all three shapes n8n may return:
1. `{ Name: ["Oliver", "Luka", "Benni"] }` — single object, Name is an array (current)
2. `[ { Name: "Oliver" }, ... ]` — array of objects
3. `{ Name: "Oliver" }` — single object, Name is a string
### POST — submit scan
Called automatically when the user types/stops typing for 100ms (debounced).
```
POST <base_url>
Authorization: Basic <base64>
Content-Type: application/json
{ "user": "Oliver", "code": "SCANNED_BARCODE_STRING" }
```
Success response:
```json
{
"name": "Christa Gierdahl, Christa Gierdahl",
"producttag": "0-HR-Nail-Care-5",
"batch": "BATCH/25-11-14/06173",
"country": "DE"
}
```
Error response (non-2xx **or** 200 with error field):
```json
{ "error": "Human-readable error description" }
```
Fields mapped to table columns:
- `country`**Country** (teal pill badge)
- `name`**Name**
- `producttag`**Tag** (monospace pill, truncated with tooltip on hover)
- `batch` → received but not displayed in the table
### Authentication
The webhook requires HTTP Basic Auth.
- On `401` (GET or POST) a modal appears asking for username + password.
- Credentials are saved to a **cookie** (`hr_auth`, 30-day expiry, `SameSite=Strict`) so the user is not prompted again.
- On page load, the cookie is read and credentials restored before the first network call.
- If the modal is triggered again (e.g. wrong password), the fields are **pre-filled** from the cookie and focus lands on the password field for quick confirmation.
- `saveCreds(user, pass)` — JSON-stringifies, URI-encodes, base64-encodes, writes cookie.
- `loadCredsFromCookie()` — reverses that; returns `{ user, pass }` or `null`.
- `clearCredsCookie()` — available but not exposed in UI (call from browser console if needed).
### Error handling (POST)
Two cases:
1. **Non-2xx response** — body is parsed; if `errData.error` exists it is shown verbatim; otherwise a generic `HTTP <status>` message is shown.
2. **200 response with `error` field** — treated as failure: `item.error` is displayed, row is *not* added to the table.
Errors appear in the **persistent status banner** (red, no auto-dismiss) and the scan input is selected so the user can re-scan or correct the code.
### GET — load batches
Called on page load and when the Batches tab becomes visible.
```
GET <batch_url>
Authorization: Basic <base64>
```
**Response shape:**
```json
[
{
"data": [
{ "id": 18599, "name": "BATCH/26-06-10/18599", "description": "mix dpd", "carrier": "DPD/AT", "state": "done" },
...
]
}
]
```
The Batches tab shows a searchable, filterable table with German headers (Chargenname, Transport, Status). Default filter resets to `state != "done"` ("Active") each time the tab opens. Columns are sortable — click Chargenname or Status to toggle ascending/descending. Sorting resets to name-asc when switching tabs.
### NDJSON / response format
n8n sometimes returns multiple items as NDJSON (one JSON object per line) instead of a proper JSON array. `parseJson(res)` handles all three formats:
1. Standard JSON array `[...]`
2. Single JSON object `{...}` — passed through as-is
3. NDJSON — split on `\n`, each line parsed individually, returned as an array
## UI layout
```
┌─────────────────────────────────────────────────────┐
│ [HR logo] Health Routine | Package Scanner │ ← white brand bar
├─────────────────────────────────────────────────────┤
│ [▥ SCAN HERE_____________] [User ▾] [✕ Clear] │ ← teal controls bar
├─────────────────────────────────────────────────────┤
│ SCAN LOG 3 scans ● │
│ ┌───────────────────────────────────────────────┐ │
│ │ COUNTRY │ NAME │ TAG │ │ ← sticky header
│ ├──────────┼───────────────────────┼─────────────┤ │
│ │ DE │ Christa Gierdahl │ 0-HR-Nail… │ │ ← newest row on top
│ │ AT │ ... │ ... │ │
│ │ scrollable area │ │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
On mobile (< 620 px): scan input goes full-width on its own row; user dropdown and clear button share the row below.
## Key behaviours
- **On load**: cookie credentials restored → GET webhooks fire (users + batches) → dropdown populated → scan input auto-focused
- **Scan auto-submit**: scanner input is debounced at 100ms — typing a barcode and pausing auto-submits without needing Enter
- **Scan auto-switches tab**: submitting a scan automatically switches to the Scan Log tab if the Batches tab is active
- **Clear button**: empties the table, clears the scan input, resets the scan counter
- **No user selected**: red toast + highlighted dropdown; does not submit
- **401 on any request**: modal prompts for credentials (pre-filled from cookie), saves to cookie, retries original call
- **Webhook error**: `error` field shown verbatim in persistent red banner; input selected for re-scan
- **Network error**: dismissing error banner; input selected so user can retry
- **Dropdown error state**: tapping/focusing the dropdown while it shows an error re-triggers `loadUsers()`
- **NDJSON/array/object**: `parseJson()` normalises all response formats from n8n
- **Duplicate scan**: `isDuplicate(code)` queries `.col-code` cells (present on pending, resolved, and error rows) before enqueuing
- **Scan queue**: `scanQueue[]` is a FIFO array of `{ code, user, rowEl }`. `drainQueue()` runs sequentially in the background — one `processItem()` at a time. Scans can be added faster than the webhook responds; each gets its own pending row immediately
- **Pending row**: shows a spinner + `Processing… <code>` while the item is in-flight; replaced in-place by the resolved row or an error row
- **Error row**: red background, shows the error message and the raw code; code is still in `.col-code` so it cannot be re-scanned
- **Queue pill**: teal badge next to the scan counter showing `N pending`; hidden when queue is empty
- **Auth mid-queue**: if `processItem` gets 401, it returns `"auth"` to `drainQueue`, which pauses (`queueBusy = false`), shows the auth modal with `drainQueue` as the retry callback — queue resumes from the same item after login
- **Clear**: also drains `scanQueue` and resets `queueBusy`
## Scan flow (sequence)
```
Enter key
└─ enqueue(code)
├─ validation (user selected? duplicate?)
├─ scanInput.value = "" ← immediate clear
├─ addPendingRow(code) ← spinner row in table
└─ scanQueue.push(...) ← add to FIFO
└─ drainQueue() if idle
└─ processItem() loop
├─ fetch POST webhook
├─ resolveRow() ← replace spinner with result
└─ rowError() ← replace spinner with error
```
## Table columns (DOM)
| Visible | Header | CSS class | Content |
|---------|--------|-----------|---------|
| ✓ | Country | — | `item.country` wrapped in teal badge |
| ✓ | Name | `name-cell` | `item.name` |
| ✓ | Tag | — | `item.producttag` in monospace pill |
| ✗ | Code | `col-code` | Raw scanned code (`display:none`), used for duplicate detection |
## How to run
Open `index.html` directly in a browser (`file://`) — no server needed for local use.
Or run the `start` script for live-reload during development:
```bash
./start # requires Node.js; installs live-server globally if missing
```