mirror of
				https://github.com/StefBuwalda/cal_counter.git
				synced 2025-10-30 03:10:00 +00:00 
			
		
		
		
	Add barcode scanning and nutrition lookup feature
Introduces a new /scan route and template for barcode scanning using ZXing in the browser. Adds a /nutri/<barcode> API endpoint to fetch food item nutrition data by barcode. Updates the FoodItem model to include a barcode field and a to_dict method for JSON serialization. Also updates seed data to include a barcode.
This commit is contained in:
		
							
								
								
									
										108
									
								
								application/templates/scan.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								application/templates/scan.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block content %} | ||||
| <div id="main" class="container text-center"> | ||||
| </div> | ||||
|  | ||||
| <template id="scan_result"> | ||||
|     <div> | ||||
|         <h5>Result:</h5> | ||||
|         <p id="result" class="fw-bold text-success"></p> | ||||
|     </div> | ||||
|  | ||||
|     <div id="product-info"></div> | ||||
| </template> | ||||
|  | ||||
| <template id="template_Reader"> | ||||
|     <h1 class="mb-4">📷 ZXing Barcode Scanner</h1> | ||||
|  | ||||
|     <div class="mb-3"> | ||||
|         <video id="video" class="border rounded shadow-sm" width="100%" style="max-width: 500px;"></video> | ||||
|     </div> | ||||
|  | ||||
|     <div class="mb-3"> | ||||
|         <button id="startButton" class="btn btn-primary">Start Scanning</button> | ||||
|         <button id="stopButton" class="btn btn-danger ms-2">Stop</button> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script type="module"> | ||||
|     import { BrowserMultiFormatReader } from 'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/+esm'; | ||||
|  | ||||
|     const mainElement = document.getElementById('main'); | ||||
|     const readerTemplate = document.getElementById('template_Reader') | ||||
|     const readerClone = readerTemplate.content.cloneNode(true); | ||||
|     const resultTemplate = document.getElementById('scan_result') | ||||
|     const resultClone = resultTemplate.content.cloneNode(true); | ||||
|     mainElement.appendChild(readerClone); | ||||
|  | ||||
|     // constants | ||||
|     const codeReader = new BrowserMultiFormatReader(); | ||||
|     const videoElement = document.getElementById('video'); | ||||
|  | ||||
|  | ||||
|     async function fetchProductData(barcode) { | ||||
|         // Step 1: GET Data | ||||
|         const response = await fetch(`/nutri/${barcode}`); | ||||
|         // Step 2: Check if response wasn't ok | ||||
|         if (!response.ok) throw new Error('Network response was not ok'); | ||||
|         // Step 3: Convert response to json and check if empty | ||||
|         const nutritionData = await response.json(); | ||||
|         mainElement.innerHTML = ''; | ||||
|         mainElement.appendChild(resultClone); | ||||
|         const resultElement = document.getElementById('result'); | ||||
|         resultElement.textContent = barcode; | ||||
|         const container = document.getElementById('product-info'); | ||||
|         if (Object.keys(nutritionData).length === 0) { | ||||
|             // No data, enter new data | ||||
|         } | ||||
|         else { | ||||
|             container.innerHTML = ` | ||||
|                     <h2>${nutritionData.name}</h2> | ||||
|                     <p><strong>Barcode:</strong> ${nutritionData.barcode}</p> | ||||
|                     <p><strong>Energy:</strong> ${nutritionData.energy_100g} kcal per 100g</p> | ||||
|                     <p><strong>Carbs:</strong> ${nutritionData.carbs_100g} g</p> | ||||
|                     <p><strong>Sugar:</strong> ${nutritionData.sugar_100g} g</p> | ||||
|                     <p><strong>Fats:</strong> ${nutritionData.fats_100g} g</p> | ||||
|                     <p><strong>Saturated Fats:</strong> ${nutritionData.saturated_fats_100g} g</p> | ||||
|                     <p><strong>Protein:</strong> ${nutritionData.protein_100g} g</p> | ||||
|                     `; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // Start scanning for barcode | ||||
|     document.getElementById('startButton').addEventListener('click', async () => { | ||||
|         console.log('[DEBUG] Start button clicked') | ||||
|         try { | ||||
|             await navigator.mediaDevices.getUserMedia({ video: true }); | ||||
|             // Use stream with video.srcObject = stream | ||||
|         } catch (err) { | ||||
|             alert("No camera found or no camera permission"); | ||||
|             console.error("Could not access the camera:", err); | ||||
|             return; | ||||
|         } | ||||
|         console.log('[DEBUG] Permission given and at least one device present'); | ||||
|         const devices = await codeReader.listVideoInputDevices(); | ||||
|         console.log('[DEBUG] Cameras found:', devices); | ||||
|         const selectedDeviceId = devices[0]?.deviceId; | ||||
|         await codeReader.decodeFromVideoDevice(selectedDeviceId, videoElement, async (result, err) => { | ||||
|             if (result) { | ||||
|                 const codeText = result.getText(); | ||||
|                 try { | ||||
|                     await fetchProductData(codeText) | ||||
|                     codeReader.reset(); | ||||
|                 } catch (error) { | ||||
|                     console.error('Error fetching nutrition data:', error); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     document.getElementById('stopButton').addEventListener('click', () => { | ||||
|         codeReader.reset(); | ||||
|         resultElement.textContent = ''; | ||||
|         container.innerHTML = "" | ||||
|     }); | ||||
| </script> | ||||
| {% endblock %} | ||||
		Reference in New Issue
	
	Block a user