From 55d6f8be7ce4b6ea8baf8b0ae9188de778a30cb7 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 25 May 2026 12:25:51 -0300 Subject: [PATCH] prevent doubles --- agent.md | 11 ++++++++++- index.html | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/agent.md b/agent.md index 4a25f26..f022f3c 100644 --- a/agent.md +++ b/agent.md @@ -132,7 +132,7 @@ On mobile (< 620 px): scan input goes full-width on its own row; user dropdown a ## Key behaviours - **On load**: cookie credentials restored → GET webhook fires → dropdown populated → scan input auto-focused -- **Enter key**: trims input, POSTs to webhook, prepends result row, clears + re-focuses input +- **Enter key**: trims input → **duplicate check** → POSTs to webhook, prepends result row, clears + re-focuses input - **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 @@ -140,6 +140,15 @@ On mobile (< 620 px): scan input goes full-width on its own row; user dropdown a - **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 in the live table before every POST; if found, shows a warning toast and skips the webhook call entirely + +## 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. diff --git a/index.html b/index.html index 7051c61..275adff 100644 --- a/index.html +++ b/index.html @@ -396,6 +396,11 @@ width: 200px; } + /* Hidden code column — stores raw scan code for duplicate checking */ + .col-code { + display: none; + } + tbody tr { border-bottom: 1px solid var(--card); animation: rowIn 0.28s ease-out; @@ -881,6 +886,7 @@ Country Name Tag + Code @@ -1201,6 +1207,15 @@ }); } + // ── Duplicate check ────────────────────────────────────────────────────── + function isDuplicate(code) { + const cells = tableBody.querySelectorAll(".col-code"); + for (const cell of cells) { + if (cell.textContent === code.trim()) return true; + } + return false; + } + // ── Submit Scan ────────────────────────────────────────────────────────── async function submitScan(code) { const user = userSelect.value; @@ -1216,6 +1231,17 @@ return; } + // Duplicate check against already-scanned codes in the table + if (isDuplicate(code)) { + showToast( + "⚠️ Already scanned: " + code.trim(), + "fail", + 4000, + ); + scanInput.select(); + return; + } + showBanner("Looking up package…", "loading"); try { @@ -1253,7 +1279,7 @@ return; } hideBanner(); - addRow(item); + addRow(item, code); showToast( "✓ Added: " + (item.name || item.producttag || code), "ok", @@ -1273,8 +1299,8 @@ } } - // ── Table Row ──────────────────────────────────────────────────────────── - function addRow(item) { + // ── Table Row ─────────────────────────────────────────────────────────────────── + function addRow(item, code) { emptyState.style.display = "none"; scanCount++; countPill.textContent = @@ -1286,6 +1312,7 @@ ${esc(item.country || "—")} ${esc(item.name || "—")} ${esc(item.producttag || "—")} + ${esc(code.trim())} `; // Newest row at the top tableBody.insertBefore(tr, tableBody.firstChild);