Email: doctorseeds@gmail.com
Phone: +91-9925-86-56-76
Solar Agrotech Private Limited,
Doctor Seeds Complex,
Bhaichand Mehta Industrial Estate, Gondal Road, Vavdi, Rajkot, Gujarat - 360-004, India.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<title>Packaging QR Tracker</title>
<style>
:root {
--primary-color: #007AFF;
--danger-color: #FF3B30;
--success-color: #34C759;
--background: #FFFFFF;
--text: #000000;
--card-bg: #F2F2F7;
--border: #E5E5EA;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #1C1C1E;
--text: #FFFFFF;
--card-bg: #2C2C2E;
--border: #3A3A3C;
}
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--background);
color: var(--text);
padding: 16px;
max-width: 100%;
overflow-x: hidden;
line-height: 1.5;
}
h1, h2 { margin-bottom: 12px; }
.card {
background-color: var(--card-bg);
border-radius: 10px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.scanner-container {
width: 100%;
height: 250px;
background: black;
position: relative;
margin: 12px 0;
border-radius: 8px;
overflow: hidden;
}
.scanner-video {
width: 100%;
height: 100%;
object-fit: cover;
}
.scanner-overlay {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: white;
background: rgba(0,0,0,0.5);
font-weight: bold;
}
button {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
padding: 12px 16px;
font-size: 16px;
width: 100%;
margin: 4px 0;
cursor: pointer;
}
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.danger { background-color: var(--danger-color); }
button.success { background-color: var(--success-color); }
.button-group {
display: flex;
gap: 8px;
margin: 8px 0;
}
.button-group button { flex: 1; }
.code-display {
margin: 12px 0;
padding: 12px;
background-color: var(--card-bg);
border-radius: 8px;
word-break: break-all;
}
.association-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: 8px;
margin-top: 12px;
}
.association-item {
padding: 12px;
border-bottom: 1px solid var(--border);
}
.association-item:last-child { border-bottom: none; }
.counter {
font-size: 0.9em;
color: #8E8E93;
margin-left: 8px;
}
.status {
margin: 8px 0;
font-size: 0.9em;
color: #8E8E93;
}
input {
width: 100%;
padding: 12px;
border: 1px solid var(--border);
border-radius: 8px;
margin: 8px 0;
background-color: var(--card-bg);
color: var(--text);
font-size: 16px;
}
</style>
</head>
<body>
<h1>Packaging QR Tracker</h1>
<p class="status">Associate 1 secondary package with up to 30 primary packages</p>
<div class="card">
<h2>Secondary Package <span id="secondary-status" class="counter">(Not scanned)</span></h2>
<div class="scanner-container">
<video id="secondary-video" class="scanner-video" playsinline></video>
<div class="scanner-overlay" id="secondary-overlay">Point at secondary QR code</div>
</div>
<div class="button-group">
<button id="scan-secondary-btn">Scan QR Code</button>
<button id="manual-secondary-btn">Enter Manually</button>
</div>
<div class="code-display">Secondary Code: <span id="current-secondary">None</span></div>
</div>
<div class="card">
<h2>Primary Packages <span id="primary-counter" class="counter">(0/30)</span></h2>
<div class="scanner-container">
<video id="primary-video" class="scanner-video" playsinline></video>
<div class="scanner-overlay" id="primary-overlay">Point at primary QR code</div>
</div>
<div class="button-group">
<button id="scan-primary-btn">Scan QR Code</button>
<button id="manual-primary-btn">Enter Manually</button>
<button id="record-primary-btn" class="success" disabled>Record & Next</button>
<button id="clear-primary-btn" class="danger">Clear All</button>
</div>
<div class="code-display">Current Primary: <span id="current-primary">None</span></div>
</div>
<div class="card">
<h2>Current Batch</h2>
<div id="current-batch" class="association-list">
<div class="association-item">No packages added yet</div>
</div>
<button id="save-batch-btn" class="success" disabled>Save Batch</button>
</div>
<div class="card">
<h2>Saved Batches</h2>
<div id="saved-batches" class="association-list">
<div class="association-item">No saved batches yet</div>
</div>
<div class="button-group">
<button id="export-btn">Export CSV</button>
<button id="clear-all-btn" class="danger">Clear All Data</button>
</div>
</div>
<script>
// App state
const state = {
secondaryCode: null,
primaryCodes: [],
batches: [],
pendingPrimary: null // NEW: holds the scanned but not yet recorded code
};
// DOM elements
const secondaryVideo = document.getElementById('secondary-video');
const primaryVideo = document.getElementById('primary-video');
const secondaryOverlay = document.getElementById('secondary-overlay');
const primaryOverlay = document.getElementById('primary-overlay');
const scanSecondaryBtn = document.getElementById('scan-secondary-btn');
const scanPrimaryBtn = document.getElementById('scan-primary-btn');
const manualSecondaryBtn = document.getElementById('manual-secondary-btn');
const manualPrimaryBtn = document.getElementById('manual-primary-btn');
const recordPrimaryBtn = document.getElementById('record-primary-btn');
const clearPrimaryBtn = document.getElementById('clear-primary-btn');
const saveBatchBtn = document.getElementById('save-batch-btn');
const exportBtn = document.getElementById('export-btn');
const clearAllBtn = document.getElementById('clear-all-btn');
const currentSecondarySpan = document.getElementById('current-secondary');
const currentPrimarySpan = document.getElementById('current-primary');
const primaryCounterSpan = document.getElementById('primary-counter');
const secondaryStatusSpan = document.getElementById('secondary-status');
const currentBatchDiv = document.getElementById('current-batch');
const savedBatchesDiv = document.getElementById('saved-batches');
// Scanner variables
let secondaryStream = null;
let primaryStream = null;
let isScanningSecondary = false;
let isScanningPrimary = false;
// Initialize app
document.addEventListener('DOMContentLoaded', () => {
loadSavedData();
setupEventListeners();
updateUI();
});
// Set up event listeners
function setupEventListeners() {
// Secondary package actions
scanSecondaryBtn.addEventListener('click', toggleSecondaryScanner);
manualSecondaryBtn.addEventListener('click', () => {
const code = prompt('Enter secondary package QR code:');
if (code && code.trim()) {
setSecondaryCode(code.trim());
}
});
// Primary package actions
scanPrimaryBtn.addEventListener('click', togglePrimaryScanner);
manualPrimaryBtn.addEventListener('click', () => {
const code = prompt('Enter primary package QR code:');
if (code && code.trim()) {
handleScannedPrimary(code.trim());
}
});
recordPrimaryBtn.addEventListener('click', () => {
if (state.pendingPrimary) {
addPrimaryCode(state.pendingPrimary);
state.pendingPrimary = null;
currentPrimarySpan.textContent = 'None';
updateUI();
}
});
clearPrimaryBtn.addEventListener('click', clearPrimaryCodes);
// Batch actions
saveBatchBtn.addEventListener('click', saveCurrentBatch);
exportBtn.addEventListener('click', exportToCSV);
clearAllBtn.addEventListener('click', confirmClearAllData);
}
// Scanner functions
async function toggleSecondaryScanner() {
if (isScanningSecondary) {
stopSecondaryScanner();
} else {
await startSecondaryScanner();
}
}
async function togglePrimaryScanner() {
if (isScanningPrimary) {
stopPrimaryScanner();
} else {
await startPrimaryScanner();
}
}
async function startSecondaryScanner() {
try {
secondaryStream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 1280 },
height: { ideal: 720 }
},
audio: false
});
secondaryVideo.srcObject = secondaryStream;
await secondaryVideo.play();
secondaryOverlay.style.display = 'none';
isScanningSecondary = true;
scanSecondaryBtn.textContent = 'Stop Scanning';
// Start scanning for QR codes
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const scanFrame = async () => {
if (!isScanningSecondary) return;
try {
if (secondaryVideo.readyState >= secondaryVideo.HAVE_ENOUGH_DATA) {
canvas.width = secondaryVideo.videoWidth;
canvas.height = secondaryVideo.videoHeight;
context.drawImage(secondaryVideo, 0, 0, canvas.width, canvas.height);
if ('BarcodeDetector' in window) {
const detector = new BarcodeDetector();
const barcodes = await detector.detect(canvas);
if (barcodes.length > 0) {
setSecondaryCode(barcodes[0].rawValue);
stopSecondaryScanner();
return;
}
} else if (typeof jsQR !== 'undefined') {
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
setSecondaryCode(code.data);
stopSecondaryScanner();
return;
}
}
}
requestAnimationFrame(scanFrame);
} catch (error) {
console.error('Scan error:', error);
requestAnimationFrame(scanFrame);
}
};
scanFrame();
} catch (error) {
alert('Could not access camera: ' + error.message);
stopSecondaryScanner();
}
}
function stopSecondaryScanner() {
if (secondaryStream) {
secondaryStream.getTracks().forEach(track => track.stop());
secondaryStream = null;
}
secondaryVideo.srcObject = null;
secondaryOverlay.style.display = 'flex';
isScanningSecondary = false;
scanSecondaryBtn.textContent = 'Scan QR Code';
}
async function startPrimaryScanner() {
try {
primaryStream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: 'environment',
width: { ideal: 1280 },
height: { ideal: 720 }
},
audio: false
});
primaryVideo.srcObject = primaryStream;
await primaryVideo.play();
primaryOverlay.style.display = 'none';
isScanningPrimary = true;
scanPrimaryBtn.textContent = 'Stop Scanning';
// Start scanning for QR codes
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const scanFrame = async () => {
if (!isScanningPrimary) return;
try {
if (primaryVideo.readyState >= primaryVideo.HAVE_ENOUGH_DATA) {
canvas.width = primaryVideo.videoWidth;
canvas.height = primaryVideo.videoHeight;
context.drawImage(primaryVideo, 0, 0, canvas.width, canvas.height);
if ('BarcodeDetector' in window) {
const detector = new BarcodeDetector();
const barcodes = await detector.detect(canvas);
if (barcodes.length > 0) {
handleScannedPrimary(barcodes[0].rawValue);
stopPrimaryScanner();
return;
}
} else if (typeof jsQR !== 'undefined') {
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const code = jsQR(imageData.data, imageData.width, imageData.height);
if (code) {
handleScannedPrimary(code.data);
stopPrimaryScanner();
return;
}
}
}
requestAnimationFrame(scanFrame);
} catch (error) {
console.error('Scan error:', error);
requestAnimationFrame(scanFrame);
}
};
scanFrame();
} catch (error) {
alert('Could not access camera: ' + error.message);
stopPrimaryScanner();
}
}
function stopPrimaryScanner() {
if (primaryStream) {
primaryStream.getTracks().forEach(track => track.stop());
primaryStream = null;
}
primaryVideo.srcObject = null;
primaryOverlay.style.display = 'flex';
isScanningPrimary = false;
scanPrimaryBtn.textContent = 'Scan QR Code';
}
// Code management
function setSecondaryCode(code) {
state.secondaryCode = code;
currentSecondarySpan.textContent = code;
secondaryStatusSpan.textContent = '(Scanned)';
updateUI();
renderCurrentBatch();
}
function handleScannedPrimary(code) {
if (state.primaryCodes.length >= 30) {
alert('Maximum of 30 primary packages reached');
stopPrimaryScanner();
return;
}
if (state.primaryCodes.includes(code)) {
alert('This primary package has already been added');
return;
}
state.pendingPrimary = code;
currentPrimarySpan.textContent = code;
updateUI();
}
function addPrimaryCode(code) {
if (state.primaryCodes.length >= 30) {
alert('Maximum of 30 primary packages reached');
stopPrimaryScanner();
return;
}
if (!state.primaryCodes.includes(code)) {
state.primaryCodes.push(code);
updateUI();
renderCurrentBatch();
} else {
alert('This primary package has already been added');
}
}
function clearPrimaryCodes() {
state.primaryCodes = [];
state.pendingPrimary = null;
currentPrimarySpan.textContent = 'None';
updateUI();
renderCurrentBatch();
}
// Batch management
function saveCurrentBatch() {
if (!state.secondaryCode || state.primaryCodes.length === 0) {
alert('Please scan both secondary and at least one primary package');
return;
}
const batch = {
secondaryCode: state.secondaryCode,
primaryCodes: [...state.primaryCodes],
timestamp: new Date().toISOString(),
displayDate: new Date().toLocaleString()
};
state.batches.push(batch);
saveData();
// Reset for next batch
state.secondaryCode = null;
state.primaryCodes = [];
state.pendingPrimary = null;
currentSecondarySpan.textContent = 'None';
currentPrimarySpan.textContent = 'None';
secondaryStatusSpan.textContent = '(Not scanned)';
updateUI();
renderCurrentBatch();
renderSavedBatches();
alert(`Batch saved with ${batch.primaryCodes.length} primary packages`);
}
// Data persistence
function saveData() {
localStorage.setItem('qrPackagingData', JSON.stringify(state.batches));
}
function loadSavedData() {
const saved = localStorage.getItem('qrPackagingData');
if (saved) {
state.batches = JSON.parse(saved);
renderSavedBatches();
}
}
function confirmClearAllData() {
if (confirm('Are you sure you want to delete all saved batches?')) {
localStorage.removeItem('qrPackagingData');
state.batches = [];
renderSavedBatches();
}
}
// Export
function exportToCSV() {
if (state.batches.length === 0) {
alert('No data to export');
return;
}
let csv = 'Secondary QR Code,Primary QR Codes,Date\n';
state.batches.forEach(batch => {
csv += `"${batch.secondaryCode}","${batch.primaryCodes.join(',')}","${batch.displayDate}"\n`;
});
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `packaging_data_${new Date().toISOString().slice(0, 10)}.csv`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
// UI rendering
function updateUI() {
primaryCounterSpan.textContent = `(${state.primaryCodes.length}/30)`;
saveBatchBtn.disabled = !(state.secondaryCode && state.primaryCodes.length > 0);
clearPrimaryBtn.disabled = state.primaryCodes.length === 0 && !state.pendingPrimary;
recordPrimaryBtn.disabled = !state.pendingPrimary;
}
function renderCurrentBatch() {
currentBatchDiv.innerHTML = '';
if (state.secondaryCode) {
const item = document.createElement('div');
item.className = 'association-item';
item.innerHTML = `<strong>Secondary:</strong> ${state.secondaryCode}`;
currentBatchDiv.appendChild(item);
}
if (state.primaryCodes.length === 0) {
const item = document.createElement('div');
item.className = 'association-item';
item.textContent = 'No primary packages added yet';
currentBatchDiv.appendChild(item);
} else {
state.primaryCodes.forEach((code, index) => {
const item = document.createElement('div');
item.className = 'association-item';
item.innerHTML = `<strong>Primary #${index + 1}:</strong> ${code}`;
currentBatchDiv.appendChild(item);
});
const summary = document.createElement('div');
summary.className = 'association-item';
summary.innerHTML = `<strong>Total:</strong> ${state.primaryCodes.length} primary package(s)`;
currentBatchDiv.appendChild(summary);
}
}
function renderSavedBatches() {
savedBatchesDiv.innerHTML = '';
if (state.batches.length === 0) {
const item = document.createElement('div');
item.className = 'association-item';
item.textContent = 'No saved batches yet';
savedBatchesDiv.appendChild(item);
return;
}
state.batches.forEach((batch, index) => {
const item = document.createElement('div');
item.className = 'association-item';
item.innerHTML = `
<div><strong>Batch ${index + 1}</strong></div>
<div><strong>Secondary:</strong> ${batch.secondaryCode}</div>
<div><strong>Primaries:</strong> ${batch.primaryCodes.length} packages</div>
<div><small>${batch.displayDate}</small></div>
`;
savedBatchesDiv.appendChild(item);
});
}
// Load jsQR library if BarcodeDetector isn't available
if (!('BarcodeDetector' in window)) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js';
document.head.appendChild(script);
}
</script>
</body>
</html>