Files
addlivephoto/views/js/admin.js
2025-11-25 11:00:26 +02:00

254 lines
8.5 KiB
JavaScript
Raw Permalink 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.

document.addEventListener('DOMContentLoaded', () => {
// 1. Elements
const video = document.getElementById('alp-video');
const canvas = document.getElementById('alp-canvas');
const overlayText = document.getElementById('alp-status-text');
const stepScan = document.getElementById('alp-step-scan');
const stepAction = document.getElementById('alp-step-action');
// Product Data Elements
const productNameEl = document.getElementById('alp-product-name');
const photoListEl = document.getElementById('alp-photo-list');
// Buttons
const manualForm = document.getElementById('alp-manual-form');
const btnExpiry = document.getElementById('btn-snap-expiry');
const btnPkg = document.getElementById('btn-snap-packaging');
const btnReset = document.getElementById('btn-reset');
// State
let currentStream = null;
let barcodeDetector = null;
let isScanning = false;
let currentProductId = null;
// 2. Initialize
initBarcodeDetector();
startCamera();
// 3. Event Listeners
manualForm.addEventListener('submit', (e) => {
e.preventDefault();
const val = document.getElementById('alp-manual-input').value.trim();
if(val) fetchProduct(val);
});
btnReset.addEventListener('click', resetApp);
btnExpiry.addEventListener('click', () => takePhoto('expiry'));
btnPkg.addEventListener('click', () => takePhoto('packaging'));
// --- Core Functions ---
async function initBarcodeDetector() {
if ('BarcodeDetector' in window) {
// Check supported formats
const formats = await BarcodeDetector.getSupportedFormats();
if (formats.includes('ean_13')) {
barcodeDetector = new BarcodeDetector({ formats: ['ean_13'] });
console.log('BarcodeDetector ready');
} else {
overlayText.textContent = "EAN13 not supported by device";
}
} else {
console.warn('BarcodeDetector API not supported in this browser');
overlayText.textContent = "Auto-scan not supported. Use manual input.";
}
}
async function startCamera() {
try {
const constraints = {
video: {
facingMode: 'environment', // Rear camera
width: { ideal: 1280 },
height: { ideal: 720 }
}
};
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = currentStream;
// Wait for video to be ready
video.onloadedmetadata = () => {
video.play();
if(barcodeDetector) {
isScanning = true;
overlayText.textContent = "Scan Barcode...";
scanLoop();
} else {
overlayText.textContent = "Camera Ready (Manual Mode)";
}
};
} catch (err) {
console.error(err);
overlayText.textContent = "Camera Access Denied or Error";
}
}
async function scanLoop() {
if (!isScanning || !barcodeDetector || currentProductId) return;
try {
const barcodes = await barcodeDetector.detect(video);
if (barcodes.length > 0) {
const code = barcodes[0].rawValue;
isScanning = false; // Stop scanning
fetchProduct(code);
return;
}
} catch (e) {
// Detection error (common in loop)
}
// Scan every 200ms to save battery
setTimeout(() => requestAnimationFrame(scanLoop), 200);
}
function fetchProduct(identifier) {
overlayText.textContent = "Searching...";
isScanning = false;
const fd = new FormData();
fd.append('action', 'searchProduct');
fd.append('identifier', identifier);
fetch(window.alpAjaxUrl, { method: 'POST', body: fd })
.then(res => res.json())
.then(data => {
if (data.success) {
loadProductView(data.data);
} else {
alert(data.message || 'Product not found');
resetApp(); // Go back to scanning
}
})
.catch(err => {
console.error(err);
alert('Network Error');
resetApp();
});
}
function loadProductView(productData) {
currentProductId = productData.id_product;
productNameEl.textContent = `[${productData.reference}] ${productData.name}`;
renderPhotos(productData.existing_photos);
// Switch View
stepScan.style.display = 'none';
stepAction.style.display = 'block';
}
function takePhoto(type) {
if (!currentProductId) return;
// Capture frame
const w = video.videoWidth;
const h = video.videoHeight;
// Crop to square (center)
const size = Math.min(w, h);
const x = (w - size) / 2;
const y = (h - size) / 2;
canvas.width = 800;
canvas.height = 800;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, x, y, size, size, 0, 0, 800, 800);
const dataUrl = canvas.toDataURL('image/webp', 0.8);
// Upload
const fd = new FormData();
fd.append('action', 'uploadImage');
fd.append('id_product', currentProductId);
fd.append('image_type', type);
fd.append('imageData', dataUrl);
// Visual feedback
const btn = (type === 'expiry') ? btnExpiry : btnPkg;
const originalText = btn.innerHTML;
btn.innerHTML = 'Uploading...';
btn.disabled = true;
fetch(window.alpAjaxUrl, { method: 'POST', body: fd })
.then(res => res.json())
.then(data => {
if (data.success) {
// Add new photo to list without reload
addPhotoToDom(data.photo);
// Flash success message
alert(`Saved as ${type}!`);
} else {
alert('Error: ' + data.message);
}
})
.catch(err => alert('Upload failed'))
.finally(() => {
btn.innerHTML = originalText;
btn.disabled = false;
});
}
function renderPhotos(photos) {
photoListEl.innerHTML = '';
if(photos && photos.length) {
photos.forEach(addPhotoToDom);
} else {
photoListEl.innerHTML = '<p class="text-muted">No photos yet.</p>';
}
}
function addPhotoToDom(photo) {
// Remove "No photos" msg if exists
if (photoListEl.querySelector('p')) photoListEl.innerHTML = '';
const div = document.createElement('div');
div.className = 'alp-thumb';
const badgeClass = (photo.type === 'expiry') ? 'badge-success' : 'badge-info';
div.innerHTML = `
<img src="${photo.url}" target="_blank">
<span class="badge ${badgeClass}">${photo.type}</span>
<button class="btn-delete" onclick="deletePhoto(${currentProductId}, '${photo.name}', this)">×</button>
`;
photoListEl.prepend(div);
}
function resetApp() {
currentProductId = null;
document.getElementById('alp-manual-input').value = '';
stepAction.style.display = 'none';
stepScan.style.display = 'block';
if(barcodeDetector) {
isScanning = true;
overlayText.textContent = "Scan Barcode...";
scanLoop();
} else {
overlayText.textContent = "Camera Ready (Manual Mode)";
}
}
// Expose delete function globally so onclick in HTML works
window.deletePhoto = function(idProduct, imgName, btnEl) {
if(!confirm('Delete this photo?')) return;
const fd = new FormData();
fd.append('action', 'deleteImage');
fd.append('id_product', idProduct);
fd.append('image_name', imgName);
fetch(window.alpAjaxUrl, { method: 'POST', body: fd })
.then(res => res.json())
.then(data => {
if(data.success) {
btnEl.closest('.alp-thumb').remove();
} else {
alert('Error deleting');
}
});
};
});