The QR scanning module lets warehouse operators point their device camera at a printed QR code to instantly populate an inbound or outbound shipment form — eliminating manual data entry for common fields like HAWB, MAWB, client name, and plate number. On the Inventory page, the same scanner doubles as a fast search tool, using the decoded value as a live filter term instead of filling a form.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/raceliciouss/YUSEN-LIMO-WAREHOUSE/llms.txt
Use this file to discover all available pages before exploring further.
Triggering a Scan
The scan flow starts from a button click in one of two contexts:- Inbound / Outbound page — click the Scan button (
.scan-btnor.inline-scan-btn) inside the active inbound or outbound tab. The scanner decodes the QR payload and callspopulateShipmentForm()to write each field value directly into the open form. - Inventory page — click the QR scan button. After a successful decode, the result is passed to
applyScannedPayloadToPage(), which writes the decoded HAWB, MAWB, or plain-text value into#inventorySearchInputand immediately re-runs the inventory filter.
#scanModal dialog, which contains a <video id="cameraPreview"> element that displays the live camera stream while the scanner is active.
Camera API Integration
startQrScanner(modal, context) orchestrates the full camera-to-decode pipeline. It is called automatically when a scan trigger is clicked.
Request camera access
The browser calls
navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } }, audio: false }). The environment facing mode prefers the rear camera on mobile devices, which is more practical for scanning printed labels.Render the live stream
The returned
MediaStream is assigned to video.srcObject and played immediately. The live feed appears in the #scanModal preview element (<video id="cameraPreview">), giving the operator real-time visual feedback while they position the code.Capture frames via canvas
On each animation frame (driven by
requestAnimationFrame), a temporary off-screen <canvas> element is sized to match the current video dimensions. context2d.drawImage(video, 0, 0, canvas.width, canvas.height) copies the current frame into the canvas, and context2d.getImageData() extracts the raw RGBA pixel buffer.Decode with jsQR
The pixel buffer is passed to
window.jsQR(imageData.data, imageData.width, imageData.height). jsQR scans the buffer for a QR pattern and returns an object containing a data string when a valid code is found, or null when the frame contains no readable code. Frames that return null are discarded and the loop continues.Pass the decoded text to handleScannedPayload()
When
jsQR returns a non-null result, result.data (the raw text string embedded in the QR code) is immediately forwarded to handleScannedPayload(text, modal, context). The scan loop is halted at this point — the app does not continue scanning after a successful read.Normalize field aliases with normalizeShipmentPayload()
handleScannedPayload() attempts JSON.parse(text). If parsing succeeds, the resulting object is passed to normalizeShipmentPayload(payload), which maps every recognized property alias (e.g., HAWB, hawbNumber, shipmentId) to a single canonical key (e.g., hawb). The function always returns a complete, predictable object regardless of which alias names were used in the source QR code.Write values to the form with populateShipmentForm()
The normalized payload is handed to
populateShipmentForm(payload, context). This function queries the active form context for every element that carries a data-field attribute and sets its value to the matching key from the normalized payload. <select> elements are handled by applySelectValue(), which performs case-insensitive, whitespace-tolerant option matching. Standard <input> elements receive .value assignment followed by a synthetic input event so any reactive listeners update correctly. If a stored shipment matching the scanned HAWB or MAWB is found in Local Storage, location and unit are also back-filled from that record.Stop the camera and close the modal
Once the form has been populated,
stopQrScanner() stops all active MediaStreamTrack objects from cameraStream, clears the requestAnimationFrame loop, and removes the active class from the camera box. The #scanModal is hidden and its inbound/outbound context classes are removed.Camera Permission Handling
startQrScanner() wraps the getUserMedia call in a try/catch block. Two distinct failure modes are handled gracefully:
- Device or browser not supported — if
navigator.mediaDevicesorgetUserMediais not present on thewindowobject, the function writes"Camera not supported on this device or browser."into the.camera-fallbackelement inside#scanModaland returns early without throwing. - Permission denied or hardware error — if the
getUserMediapromise rejects (e.g., the user clicks “Block” in the browser permission dialog, or the camera is in use by another application), the catch block writes"Unable to access camera. Please allow camera permission or use a compatible device."into.camera-fallbackand logs the error to the console. The modal remains open so the operator can read the message.
Camera scanning requires a secure context. The
getUserMedia API is restricted by browsers to pages served over https:// or from localhost. Opening the app via the file:// protocol — for example, by double-clicking index.html in a file manager — will cause navigator.mediaDevices to be undefined, and the scanner will fall back to the unsupported-device message. Always serve the project through a local web server (e.g., python3 -m http.server 8080) or a TLS-enabled host.