Files
CSG-Picker-Shipper-Helper/app/assets/picker.html
T
2026-07-06 00:24:30 -05:00

1004 lines
35 KiB
HTML
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.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSG Picker & Verifier — CollisionStoneGuard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/html5-qrcode"></script>
<style>
:root {
--bg-color: #0d0f12;
--panel-bg: rgba(22, 26, 33, 0.85);
--border-color: rgba(255, 255, 255, 0.08);
--accent-color: #3b82f6;
--accent-hover: #2563eb;
--text-primary: #f3f4f6;
--text-secondary: #9ca3af;
--success-color: #10b981;
--error-color: #ef4444;
--warning-color: #f59e0b;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
/* Glassmorphism Header */
header {
background: rgba(13, 15, 18, 0.7);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-color);
padding: 1rem 1.5rem;
position: sticky;
top: 0;
z-index: 100;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-brand {
display: flex;
align-items: center;
gap: 0.75rem;
}
.logo-dot {
width: 12px;
height: 12px;
background-color: var(--accent-color);
border-radius: 50%;
box-shadow: 0 0 12px var(--accent-color);
}
h1 {
font-size: 1.15rem;
font-weight: 600;
letter-spacing: -0.025em;
}
.header-brand p {
font-size: 0.75rem;
color: var(--text-secondary);
}
.connection-badge {
font-size: 0.75rem;
background-color: rgba(16, 185, 129, 0.15);
color: var(--success-color);
padding: 0.25rem 0.5rem;
border-radius: 9999px;
border: 1px solid rgba(16, 185, 129, 0.2);
display: flex;
align-items: center;
gap: 0.35rem;
}
.connection-dot {
width: 6px;
height: 6px;
background-color: var(--success-color);
border-radius: 50%;
}
/* Main Workspace */
main {
flex: 1;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
max-width: 1600px;
width: 100%;
margin: 0 auto;
gap: 1.5rem;
padding: 1.5rem;
}
@media (max-width: 968px) {
main {
grid-template-columns: 1fr;
}
}
/* Left Side: Invoice details & List */
.workspace-panel {
background-color: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
backdrop-filter: blur(8px);
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 1rem;
height: calc(100vh - 120px);
min-height: 500px;
}
@media (max-width: 968px) {
.workspace-panel {
height: auto;
min-height: auto;
}
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.75rem;
}
.panel-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Input Controls */
.input-group {
display: flex;
gap: 0.5rem;
}
input[type="text"] {
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
padding: 0.6rem 0.8rem;
border-radius: 8px;
color: var(--text-primary);
font-family: inherit;
font-size: 0.9rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
input[type="text"]:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
button {
background-color: var(--accent-color);
color: var(--text-primary);
border: none;
padding: 0.6rem 1rem;
border-radius: 8px;
font-family: inherit;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}
button:hover {
background-color: var(--accent-hover);
}
button:active {
transform: scale(0.98);
}
.btn-secondary {
background-color: rgba(255, 255, 255, 0.08);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background-color: rgba(255, 255, 255, 0.12);
}
/* Invoice Summary Info */
.invoice-card {
background-color: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.info-item {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.info-label {
font-size: 0.7rem;
color: var(--text-secondary);
text-transform: uppercase;
}
.info-val {
font-size: 0.9rem;
font-weight: 600;
}
/* Parts List Scrollable container */
.parts-container {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.75rem;
padding-right: 0.25rem;
}
/* Custom Scrollbar */
.parts-container::-webkit-scrollbar {
width: 6px;
}
.parts-container::-webkit-scrollbar-track {
background: transparent;
}
.parts-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.part-card {
background-color: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s, transform 0.1s;
position: relative;
overflow: hidden;
}
.part-card::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background-color: transparent;
transition: background-color 0.2s;
}
.part-card:hover {
background-color: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.15);
}
.part-card.active {
background-color: rgba(59, 130, 246, 0.05);
border-color: var(--accent-color);
}
.part-card.active::before {
background-color: var(--accent-color);
}
.part-card.picked {
background-color: rgba(16, 185, 129, 0.05);
border-color: rgba(16, 185, 129, 0.3);
}
.part-card.picked::before {
background-color: var(--success-color);
}
.part-details-left {
display: flex;
flex-direction: column;
gap: 0.35rem;
flex: 1;
}
.part-sku {
font-size: 0.95rem;
font-weight: 700;
letter-spacing: 0.02em;
display: flex;
align-items: center;
gap: 0.5rem;
}
.part-desc {
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.3;
}
.tag-row {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-top: 0.2rem;
}
.tag {
font-size: 0.7rem;
padding: 0.15rem 0.4rem;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.06);
color: var(--text-secondary);
font-weight: 500;
}
.tag.bin {
background-color: rgba(245, 158, 11, 0.1);
color: var(--warning-color);
border: 1px solid rgba(245, 158, 11, 0.15);
}
.tag.qty {
background-color: rgba(59, 130, 246, 0.1);
color: var(--accent-color);
}
.part-status {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
transition: background-color 0.2s, border-color 0.2s;
}
.picked .part-status {
background-color: var(--success-color);
border-color: var(--success-color);
color: white;
}
.part-card.unavailable {
background-color: rgba(239, 68, 68, 0.05);
border-color: rgba(239, 68, 68, 0.3);
opacity: 0.8;
}
.part-card.unavailable::before {
background-color: var(--error-color);
}
.unavailable .part-status {
background-color: var(--error-color);
border-color: var(--error-color);
color: white;
}
/* Right Side: Camera View & Selected Detail */
.camera-panel {
display: flex;
flex-direction: column;
gap: 1rem;
}
.scanner-card {
background-color: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.camera-viewport {
width: 100%;
aspect-ratio: 4/3;
background-color: black;
border-radius: 8px;
overflow: hidden;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.05);
}
/* Visual guide/targeting box for scan */
.scanner-laser {
position: absolute;
top: 50%;
left: 10%;
right: 10%;
height: 2px;
background-color: var(--accent-color);
box-shadow: 0 0 8px var(--accent-color);
animation: scanning 2.5s infinite ease-in-out;
z-index: 10;
pointer-events: none;
opacity: 0.6;
}
@keyframes scanning {
0% { top: 15%; }
50% { top: 85%; }
100% { top: 15%; }
}
/* Dropdowns for camera selection */
.select-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 0.5rem;
}
select {
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
padding: 0.6rem;
border-radius: 8px;
color: var(--text-primary);
font-family: inherit;
font-size: 0.85rem;
outline: none;
}
/* Detailed Card for Active Selection */
.details-card {
background-color: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 1rem;
flex: 1;
}
.details-card-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--text-secondary);
font-size: 0.9rem;
flex: 1;
text-align: center;
min-height: 150px;
}
.details-header {
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.75rem;
}
.details-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.details-item {
background-color: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 0.75rem;
}
.map-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 0.5rem;
}
/* Modal Popup styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(4px);
z-index: 1000;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
.modal-content {
background-color: #161a21;
border: 1px solid var(--border-color);
border-radius: 16px;
max-width: 500px;
width: 100%;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
position: relative;
}
.modal-title {
font-size: 1.1rem;
font-weight: 700;
}
.modal-close {
position: absolute;
top: 1rem;
right: 1rem;
background: none;
border: none;
color: var(--text-secondary);
font-size: 1.25rem;
cursor: pointer;
padding: 0.25rem;
}
.modal-close:hover {
color: var(--text-primary);
}
/* Map Placeholder */
.map-placeholder {
width: 100%;
aspect-ratio: 16/10;
background-color: rgba(0, 0, 0, 0.4);
border: 1px dashed var(--border-color);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
font-size: 0.85rem;
text-align: center;
padding: 1rem;
}
/* Notification banner */
#notification-banner {
position: fixed;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%) translateY(100px);
padding: 0.75rem 1.5rem;
border-radius: 8px;
color: white;
font-weight: 500;
font-size: 0.9rem;
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.3);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 1100;
display: flex;
align-items: center;
gap: 0.5rem;
}
#notification-banner.show {
transform: translateX(-50%) translateY(0);
}
#notification-banner.success { background-color: var(--success-color); }
#notification-banner.error { background-color: var(--error-color); }
#notification-banner.warning { background-color: var(--warning-color); }
.hidden { display: none !important; }
.spin {
animation: spin 1.5s linear infinite;
display: inline-block;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* ── Tab navigation ────────────────────────────────── */
.tab-nav {
display: flex;
gap: 0.25rem;
background: rgba(255,255,255,0.04);
border-radius: 10px;
padding: 0.25rem;
border: 1px solid var(--border-color);
}
.tab-btn {
padding: 0.4rem 1rem;
border-radius: 7px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 0.8rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
font-family: inherit;
}
.tab-btn.active {
background: var(--accent-color);
color: #fff;
box-shadow: 0 2px 8px rgba(59,130,246,0.35);
}
.tab-btn:hover:not(.active) {
background: rgba(255,255,255,0.07);
color: var(--text-primary);
}
/* ── QR Generator tab ──────────────────────────────── */
.qr-generator-tab {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
max-width: 900px;
margin: 0 auto;
width: 100%;
}
.qr-gen-card {
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 14px;
padding: 1.25rem 1.5rem;
backdrop-filter: blur(10px);
}
.qr-gen-card h3 {
font-size: 0.95rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.qr-field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
@media (max-width: 600px) {
.qr-field-row { grid-template-columns: 1fr; }
}
.qr-field {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.qr-field label {
font-size: 0.75rem;
color: var(--text-secondary);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.qr-field input, .qr-field select {
background: rgba(255,255,255,0.05);
border: 1px solid var(--border-color);
border-radius: 8px;
color: var(--text-primary);
padding: 0.6rem 0.9rem;
font-size: 0.9rem;
font-family: inherit;
transition: border-color 0.15s;
outline: none;
}
.qr-field input:focus, .qr-field select:focus {
border-color: var(--accent-color);
}
.qr-field input[readonly] {
opacity: 0.6;
cursor: default;
}
.qr-output-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
}
.qr-output-card {
background: rgba(255,255,255,0.04);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.6rem;
transition: border-color 0.15s;
}
.qr-output-card:hover {
border-color: var(--accent-color);
}
.qr-canvas-wrap {
background: #fff;
border-radius: 8px;
padding: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.qr-sku-label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-primary);
text-align: center;
word-break: break-all;
}
.qr-bin-label {
font-size: 0.7rem;
color: var(--text-secondary);
text-align: center;
}
.qr-card-actions {
display: flex;
gap: 0.4rem;
width: 100%;
}
.qr-card-actions button {
flex: 1;
padding: 0.4rem 0;
font-size: 0.75rem;
}
</style>
</head>
<body>
<!-- App Header -->
<header>
<div class="header-brand">
<div class="logo-dot"></div>
<div>
<h1>CSG Part Verifier</h1>
<p>Warehouse Pick Assistant</p>
</div>
</div>
<div class="tab-nav">
<button class="tab-btn active" id="tab-pick-btn" onclick="switchTab('pick')">📋 Pick Queue</button>
<button class="tab-btn" id="tab-qr-btn" onclick="switchTab('qr')">🔲 QR Generator</button>
<button class="tab-btn" id="tab-master-btn" onclick="switchTab('master')">📦 Master List</button>
</div>
<div class="connection-badge">
<div class="connection-dot"></div>
<span>Connected</span>
</div>
</header>
<!-- Main Workspace -->
<main>
<!-- Left Panel: Invoices & List -->
<section class="workspace-panel">
<div class="panel-header">
<span class="panel-title">Pick Queue</span>
<span id="items-picked-count" class="tag qty">0 / 0 Items</span>
</div>
<!-- Input Invoice Row -->
<div class="input-group">
<input type="text" id="invoice-search-input" placeholder="Scan or enter Invoice # (e.g. 12345)" autocomplete="off">
<button id="invoice-search-btn">Load</button>
</div>
<!-- Active Invoice Summary -->
<div id="invoice-info-card" class="invoice-card hidden">
<div class="info-item">
<span class="info-label">Invoice #</span>
<span id="lbl-invoice-num" class="info-val"></span>
</div>
<div class="info-item">
<span class="info-label">Customer</span>
<span id="lbl-customer-name" class="info-val"></span>
</div>
</div>
<!-- Scrollable Checklist -->
<div id="parts-list" class="parts-container">
<div class="details-card-empty" id="list-empty-state">
Scan invoice QR or enter details above to populate pick list.
</div>
</div>
</section>
<!-- Right Panel: Camera & Details -->
<section class="camera-panel">
<!-- Scanner Viewport Card -->
<div class="scanner-card">
<div class="panel-header">
<span class="panel-title">Scanner</span>
<span id="scanner-status" class="tag" style="background-color: rgba(59, 130, 246, 0.15); color: var(--accent-color);">READY</span>
</div>
<div class="camera-viewport">
<div id="qr-reader" style="width: 100%; height: 100%;"></div>
<div class="scanner-laser"></div>
</div>
<div class="select-row">
<select id="camera-select">
<option value="">Detecting cameras...</option>
</select>
<button id="toggle-scan-btn" class="btn-secondary">Stop Scan</button>
</div>
</div>
<!-- Detailed Fact Card for Selected Item -->
<div class="details-card" id="part-details-panel">
<div class="details-card-empty" id="details-empty-state">
Select a part from the list to view physical locations and scan matching box codes.
</div>
<div id="part-details-content" class="hidden" style="display: flex; flex-direction: column; gap: 1rem;">
<div class="details-header">
<h2 id="det-sku" style="font-size: 1.15rem; font-weight: 700;">8620843-0423L</h2>
<p id="det-desc" style="font-size: 0.85rem; color: var(--text-secondary); margin-top: 0.25rem;">Front Bumper Cover Assy</p>
</div>
<div class="details-grid" style="grid-template-columns: repeat(3, 1fr);">
<div class="details-item">
<span class="info-label">Bin Location</span>
<span id="det-bin" class="info-val" style="color: var(--warning-color); font-size: 1.1rem; display: block; margin-top: 0.2rem;">A-12-B</span>
</div>
<div class="details-item">
<span class="info-label">Yard Area</span>
<span id="det-yard" class="info-val" style="display: block; margin-top: 0.2rem;">CSG</span>
</div>
<div class="details-item">
<span class="info-label">Required Qty</span>
<span id="det-qty" class="info-val" style="color: var(--accent-color); display: block; margin-top: 0.2rem;">1</span>
</div>
<div class="details-item">
<span class="info-label">Stock Price</span>
<span id="det-price" class="info-val" style="display: block; margin-top: 0.2rem;">$150.00</span>
</div>
<div class="details-item">
<span class="info-label">Total Stock</span>
<span id="det-stock" class="info-val" style="color: var(--success-color); display: block; margin-top: 0.2rem;">50</span>
</div>
</div>
<div class="map-section">
<button id="btn-show-map" class="btn-secondary" style="width: 100%;">🗺️ Show Part Location Map</button>
<button id="btn-reprint-label" class="btn-secondary" style="width: 100%; margin-top: 0.25rem;">🖨️ Enqueue Label Reprint</button>
<button id="btn-mark-unavailable" class="btn-secondary" style="width: 100%; margin-top: 0.25rem; background-color: rgba(239, 68, 68, 0.1); border-color: var(--error-color); color: var(--error-color);">⚠️ Mark Part Out of Stock</button>
</div>
</div>
</div>
</section>
</main>
<!-- ── QR Generator Tab ───────────────────────────────────────────────── -->
<div id="tab-qr" style="display:none; flex:1; overflow-y:auto;">
<div class="qr-generator-tab">
<!-- SKU Lookup Card -->
<div class="qr-gen-card">
<h3>🔲 Generate Part Box QR Codes</h3>
<div class="qr-field-row">
<div class="qr-field" style="grid-column: span 2;">
<label>SKU / Stock Number</label>
<div style="display:flex; gap:0.5rem;">
<input type="text" id="qr-sku-input" placeholder="e.g. 84126A7000-0152R" autocomplete="off" style="flex:1;" onkeydown="if(event.key==='Enter') lookupQrSku()">
<button onclick="lookupQrSku()" style="width:100px;">Look Up</button>
</div>
</div>
</div>
<div class="qr-field-row">
<div class="qr-field" style="grid-column: span 2;">
<label>Description (Car Make & Model)</label>
<input type="text" id="qr-desc-input" placeholder="Auto-filled from parts list" readonly>
</div>
<div class="qr-field">
<label>Bin Location</label>
<input type="text" id="qr-bin-input" placeholder="Auto-filled" readonly>
</div>
<div class="qr-field">
<label>QR Code Content</label>
<select id="qr-content-select">
<option value="sku">SKU only (recommended)</option>
<option value="sku_bin">SKU + Bin Location</option>
</select>
</div>
</div>
<div style="display:flex; gap:0.75rem; flex-wrap:wrap;">
<button id="qr-generate-btn" onclick="generateQrCode()" style="min-width:140px;">⚡ Generate QR</button>
<button onclick="addToQueue()" class="btn-secondary" style="min-width:140px;"> Add to Batch</button>
<button onclick="clearQrQueue()" class="btn-secondary" style="min-width:100px; margin-left:auto; color:var(--error-color);">🗑 Clear All</button>
</div>
</div>
<!-- Batch / Output Grid -->
<div class="qr-gen-card" id="qr-output-section">
<h3>📦 Generated QR Codes <span id="qr-count-badge" style="font-size:0.8rem; font-weight:400; color:var(--text-secondary);">— none yet</span></h3>
<div id="qr-output-grid" class="qr-output-grid">
<div style="color:var(--text-secondary); font-size:0.9rem; padding:1rem 0;">Look up a SKU above and click <strong>Generate QR</strong> or <strong>Add to Batch</strong>.</div>
</div>
<div style="margin-top:1rem; display:flex; gap:0.75rem; flex-wrap:wrap;">
<button onclick="downloadAllQr()" class="btn-secondary" style="min-width:160px;">⬇️ Download All as ZIP</button>
<button onclick="printAllQr()" class="btn-secondary" style="min-width:140px;">🖨️ Print All</button>
<a href="/picker/setup-labels" target="_blank" class="btn-secondary" style="min-width:220px; text-decoration:none; display:inline-flex; align-items:center; justify-content:center; background-color:rgba(59, 130, 246, 0.1); border-color:var(--accent-color); color:var(--text-primary); font-size:0.8rem; font-weight:500; border-radius:8px; padding:0.5rem 1rem;">⚙️ First-Time Warehouse Setup</a>
</div>
</div>
</div>
</div>
<!-- ── Master List Tab ────────────────────────────────────────────────── -->
<div id="tab-master" style="display:none; flex:1; overflow-y:auto; padding: 1.5rem;">
<div class="qr-generator-tab" style="max-width: 1000px; margin: 0 auto; width:100%;">
<div class="qr-gen-card">
<h3>📦 Master Parts Inventory & Barcodes</h3>
<div class="form-group" style="margin-top: 0.5rem; display:flex; flex-direction:column; gap:0.35rem;">
<label style="font-size: 0.75rem; color: var(--text-secondary); font-weight:500;">Search Master List (SKU or Car Make/Model/Description)</label>
<input type="text" id="master-search-input" placeholder="Type to search e.g. Volvo, Forte, 84126..." autocomplete="off" oninput="filterMasterList()" style="background: rgba(255,255,255,0.05); border: 1px solid var(--border-color); border-radius: 8px; color: var(--text-primary); padding: 0.6rem 0.9rem; font-size: 0.9rem; outline: none; width:100%;">
</div>
</div>
<div class="qr-gen-card">
<div id="master-grid" class="preview-grid" style="grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem;">
<!-- Loaded dynamically -->
</div>
<div id="master-load-more" style="text-align: center; margin-top: 1.5rem; display: none;">
<button id="btn-master-load-more" onclick="loadMoreMasterParts()" class="secondary-btn" style="min-width: 160px; padding: 0.6rem 1.2rem; font-size: 0.85rem; font-weight:600; cursor:pointer;">Load More Parts</button>
</div>
</div>
</div>
</div>
<!-- Modal Popup for Location Map -->
<div id="map-modal" class="modal">
<div class="modal-content">
<button class="modal-close" id="btn-close-map"></button>
<h3 class="modal-title">Bin Location Map</h3>
<div id="map-body-content">
<div class="map-placeholder">
Warehouse Layout Map<br>
(PNG map assets are currently on the backburner. Standard layout mappings will display here.)
</div>
</div>
<div style="display: flex; justify-content: flex-end;">
<button id="btn-close-map-ok" style="width: 100px;">Close</button>
</div>
</div>
</div>
<!-- Modal Popup for Discrepancy Confirmation -->
<div id="discrepancy-modal" class="modal">
<div class="modal-content" style="border-color: var(--error-color);">
<h3 class="modal-title" style="color: var(--error-color);">⚠️ Part Mismatch Warning</h3>
<p style="font-size: 0.9rem; color: var(--text-secondary); line-height: 1.4;">
The scanned QR code SKU (<strong id="scanned-sku-display" style="color: var(--text-primary);">SKU-123</strong>) does not match the selected invoice part SKU (<strong id="target-sku-display" style="color: var(--text-primary);">SKU-ABC</strong>).
</p>
<p style="font-size: 0.85rem; font-weight: 500;">
Are you sure you want to override and accept this part?
</p>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<button id="btn-override-yes" style="background-color: var(--success-color);">Yes, Accept</button>
<button id="btn-override-no" class="btn-secondary">No, Scan Again</button>
</div>
</div>
</div>
<!-- Notification Banner -->
<div id="notification-banner">
<span id="banner-text">Item verified!</span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script src="/assets/picker.js"></script>
</body>
</html>