prevent doubles
This commit is contained in:
@@ -132,7 +132,7 @@ On mobile (< 620 px): scan input goes full-width on its own row; user dropdown a
|
|||||||
|
|
||||||
## Key behaviours
|
## Key behaviours
|
||||||
- **On load**: cookie credentials restored → GET webhook fires → dropdown populated → scan input auto-focused
|
- **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
|
- **Clear button**: empties the table, clears the scan input, resets the scan counter
|
||||||
- **No user selected**: red toast + highlighted dropdown; does not submit
|
- **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
|
- **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
|
- **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()`
|
- **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
|
- **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
|
## How to run
|
||||||
Open `index.html` directly in a browser (`file://`) — no server needed for local use.
|
Open `index.html` directly in a browser (`file://`) — no server needed for local use.
|
||||||
|
|||||||
+30
-3
@@ -396,6 +396,11 @@
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hidden code column — stores raw scan code for duplicate checking */
|
||||||
|
.col-code {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
tbody tr {
|
tbody tr {
|
||||||
border-bottom: 1px solid var(--card);
|
border-bottom: 1px solid var(--card);
|
||||||
animation: rowIn 0.28s ease-out;
|
animation: rowIn 0.28s ease-out;
|
||||||
@@ -881,6 +886,7 @@
|
|||||||
<th>Country</th>
|
<th>Country</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Tag</th>
|
<th>Tag</th>
|
||||||
|
<th class="col-code">Code</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="tableBody"></tbody>
|
<tbody id="tableBody"></tbody>
|
||||||
@@ -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 ──────────────────────────────────────────────────────────
|
// ── Submit Scan ──────────────────────────────────────────────────────────
|
||||||
async function submitScan(code) {
|
async function submitScan(code) {
|
||||||
const user = userSelect.value;
|
const user = userSelect.value;
|
||||||
@@ -1216,6 +1231,17 @@
|
|||||||
return;
|
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");
|
showBanner("Looking up package…", "loading");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1253,7 +1279,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideBanner();
|
hideBanner();
|
||||||
addRow(item);
|
addRow(item, code);
|
||||||
showToast(
|
showToast(
|
||||||
"✓ Added: " + (item.name || item.producttag || code),
|
"✓ Added: " + (item.name || item.producttag || code),
|
||||||
"ok",
|
"ok",
|
||||||
@@ -1273,8 +1299,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Table Row ────────────────────────────────────────────────────────────
|
// ── Table Row ───────────────────────────────────────────────────────────────────
|
||||||
function addRow(item) {
|
function addRow(item, code) {
|
||||||
emptyState.style.display = "none";
|
emptyState.style.display = "none";
|
||||||
scanCount++;
|
scanCount++;
|
||||||
countPill.textContent =
|
countPill.textContent =
|
||||||
@@ -1286,6 +1312,7 @@
|
|||||||
<td><span class="country-badge">${esc(item.country || "—")}</span></td>
|
<td><span class="country-badge">${esc(item.country || "—")}</span></td>
|
||||||
<td class="name-cell">${esc(item.name || "—")}</td>
|
<td class="name-cell">${esc(item.name || "—")}</td>
|
||||||
<td><span class="tag-pill" title="${esc(item.producttag || "")}">${esc(item.producttag || "—")}</span></td>
|
<td><span class="tag-pill" title="${esc(item.producttag || "")}">${esc(item.producttag || "—")}</span></td>
|
||||||
|
<td class="col-code">${esc(code.trim())}</td>
|
||||||
`;
|
`;
|
||||||
// Newest row at the top
|
// Newest row at the top
|
||||||
tableBody.insertBefore(tr, tableBody.firstChild);
|
tableBody.insertBefore(tr, tableBody.firstChild);
|
||||||
|
|||||||
Reference in New Issue
Block a user