Skip to content

feat(inventory): scan-to-location QR assignment#1606

Open
samedyuksel wants to merge 7 commits into
maziggy:devfrom
samedyuksel:feature/inventory-qr-scan-to-location
Open

feat(inventory): scan-to-location QR assignment#1606
samedyuksel wants to merge 7 commits into
maziggy:devfrom
samedyuksel:feature/inventory-qr-scan-to-location

Conversation

@samedyuksel

@samedyuksel samedyuksel commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a "scan-to-location" mode to the inventory page: pick an assignment target (an AMS slot or a storage location) first, then scan the spool's QR to assign it there immediately — no edit modal. Hybrid scanning: a live in-page camera (BarcodeDetector → jsQR) with a native-camera Take photo fallback for small/dense QRs. A spool stays in one slot (assigning to a new AMS slot clears the old one).

Related Issue

Fixes #1574

Documentation

Companion docs PRs:

Pick one:

  • Docs PR(s) linked above

Type of Change

  • New feature (non-breaking change that adds functionality)

Changes Made

  • New QrAssignTargetModal: two-step (pick target → scan) with a live camera scanner and a photo-capture fallback
  • parseSpoolIdFromQr util (+ unit tests) extracts the spool id from the label deeplink (URL / ?spool= / bare id)
  • Move semantics: assigning to a new AMS slot clears the spool's previous slot (local + Spoolman)
  • Republish nudge after an AMS assign (parity with [Bug Report] Assigning spools to external slots failing even when all settings match #1414)
  • i18n inventory.qrAssign.* added across all 9 locales; adds the jsqr dependency

Screenshots

1 2 3 4 5

Testing

  • I have tested this on my local machine
  • I have tested with my printer model: P1S

Checklist

  • My code follows the project's coding style
  • I have commented my code where necessary
  • My changes generate no new warnings
  • I have tested my changes thoroughly

Additional Notes

  • Captured photos are decoded in-browser and discarded — never uploaded or stored.
  • The live in-page camera needs a secure (HTTPS) context; the Take photo fallback works regardless.
  • Frontend-only: no new backend endpoint (reuses the existing assignment + storage_location APIs).

@samedyuksel
samedyuksel marked this pull request as ready for review June 2, 2026 16:49
@samedyuksel
samedyuksel requested a review from maziggy as a code owner June 2, 2026 16:49

@maziggy maziggy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add it to Spoolbuddy frontend to keep feature parity.

@maziggy maziggy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(feature/inventory-qr-scan-to-location *)$ ./test_frontend.sh

frontend@0.0.0 lint
eslint .

frontend@0.0.0 test:run
vitest run && npm run check:i18n
RUN v4.1.8 /opt/claude/projects/bambuddy/frontend

✓ src/tests/i18n/parity-script.test.ts (8 tests) 10ms
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (/opt/claude/projects/bambuddy/frontend/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (/opt/claude/projects/bambuddy/frontend/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (/opt/claude/projects/bambuddy/frontend/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)
at /opt/claude/projects/bambuddy/frontend/src/components/QrAssignTargetModal.tsx:118:24
at Object.react_stack_bottom_frame (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:25989:20)
at runWithFiberInDEV (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at commitHookEffectListMount (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:13249:29)
at commitHookPassiveMountEffects (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:13336:11)
at commitPassiveMountOnFiber (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:15484:13)
at recursivelyTraversePassiveMountEffects (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:15439:11) undefined
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (/opt/claude/projects/bambuddy/frontend/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (/opt/claude/projects/bambuddy/frontend/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (/opt/claude/projects/bambuddy/frontend/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)
at /opt/claude/projects/bambuddy/frontend/src/components/QrAssignTargetModal.tsx:118:24
at Object.react_stack_bottom_frame (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:25989:20)
at runWithFiberInDEV (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:874:13)
at commitHookEffectListMount (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:13249:29)
at commitHookPassiveMountEffects (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:13336:11)
at commitPassiveMountOnFiber (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:15484:13)
at recursivelyTraversePassiveMountEffects (/opt/claude/projects/bambuddy/frontend/node_modules/react-dom/cjs/react-dom-client.development.js:15439:11) undefined


samedyuksel and others added 4 commits June 5, 2026 20:38
Add an in-page camera QR scanner to the inventory page. The user opens "Assign by QR", picks a target (AMS slot or storage location), then scans a spool's label QR; the scanned spool is assigned to that target directly instead of opening the edit modal.

- QrAssignTargetModal: two-step picker -> live getUserMedia + jsqr scanner
- Move semantics: an AMS assign clears the spool's previous slot (local + Spoolman) so a spool lives in one slot at a time
- Nudge the printer to republish after an AMS assign (maziggy#1414 parity)
- parseSpoolIdFromQr util (+ unit tests) extracts the id from the deeplink
- i18n: inventory.qrAssign.* added to all 9 locales (parity green)
- adds jsqr dependency

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…1574)

Refine the scan-to-location flow:

- Hybrid scan step: a live in-page camera scanner (BarcodeDetector -> jsQR fallback) plus a native-camera "Take photo" fallback for small/dense QRs the live preview can't focus on. Captured photos are decoded in-browser and discarded — never uploaded or stored.
- Move semantics: assigning a spool to a new AMS slot clears its previous slot (local + Spoolman) so a spool occupies one slot at a time.
- Nudge the printer to republish after an AMS assign (maziggy#1414 parity).
- parseSpoolIdFromQr handles full URLs, bare ?spool= fragments (incl. hash), and bare ids.
- Cleanups: single error state, drop unused camera-reason plumbing.
- i18n reworded across all 9 locales (parity green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ziggy#1574)

Address PR review:
- Add the "Assign by QR" scan-to-location flow to the SpoolBuddy inventory page (reuses the generic QrAssignTargetModal) for feature parity with the main inventory page.
- Guard LiveScanner's canvas.getContext so the test suite no longer errors under jsdom (HTMLCanvasElement.getContext is unimplemented there); the camera never starts in that env so a null context is harmless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@samedyuksel
samedyuksel force-pushed the feature/inventory-qr-scan-to-location branch from b1daf7a to 0988cfe Compare June 5, 2026 17:42
@samedyuksel

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Addressed both points:

  1. SpoolBuddy parity — added the "Assign by QR" scan-to-location flow to the SpoolBuddy inventory page (SpoolBuddyInventoryPage), reusing the same generic QrAssignTargetModal. Both AMS-slot and storage-location targets work there too.
  2. ./test_frontend.sh failure — the HTMLCanvasElement.getContext error under jsdom is fixed: the live scanner now guards getContext (the camera never starts in the test env, so a null context is harmless), so the suite no longer errors.

Also rebased onto the latest dev and added the inventory.qrAssign.* keys to the new ko/tr locales, so npm run check:i18n passes (11/11).

Still a draft while I attach before/after screenshots — will mark it ready for review once they're in.

…ggy#1574)

Quality cleanup (no behavior change):
- Extract distinctStorageLocations() into utils/inventorySearch and use it in InventoryPage (both the QR autocomplete and the maziggy#1400 filter datalist) and SpoolBuddyInventoryPage — removes three copies and unifies trim handling.
- Extract decodeCanvas() shared by the live scanner and the photo decoder, collapsing the duplicated canvas+jsQR block and the getContext guard into one place.
- Collapse parseSpoolIdFromQr's two-alternative regex into one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@samedyuksel
samedyuksel requested a review from maziggy June 5, 2026 18:04
@maziggy

maziggy commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Please rebase to resolve conflicts. Thanks.

@maziggy maziggy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged dev in and resolved the two conflicts in InventoryPage.tsx and SpoolBuddyInventoryPage.tsx (both in import/setup blocks where dev landed the new structured Locations + CSV import/export work). All your QrCode/storageSuggestions additions kept, no behaviour change to the scan flow. Dropped the uniqueStorageLocations const since it ended up unreferenced after the merge.

One change I'd like before merging: dev landed a structured Location entity (location_id FK) and that's now the canonical model — the Locations page, the Locations dropdown filter on the inventory page, and the LocationsModal picker all operate on location_id. Your QR-assign currently writes only to the legacy free-form storage_location string, which means a spool assigned via QR scan is invisible to the new Locations filter and to anyone using the Locations UI as the primary surface.

What I'd suggest for the storage tab in QrAssignTargetModal:

  • Replace the free-text storage_location input + autocomplete with a picker over api.getLocations() (same data source as the inventory page filter dropdown), with a "create new location" affordance — LocationsModal already exposes the pick-by-id pattern.
  • On scan confirm, write { location_id: pickedId } via api.updateSpool / api.updateSpoolmanInventorySpool instead of { storage_location: name }. Both the local and Spoolman update endpoints accept location_id.
  • The AMS-slot tab stays unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants