mirror of
https://github.com/StefBuwalda/cal_counter.git
synced 2025-10-30 11:19:59 +00:00
Add meal v2 flow and user timezone support
Introduces a new add_meal_v2 blueprint with barcode scanning, item search, and improved meal logging UI. Adds user timezone support: login form now captures timezone, User model and database schema updated, and timezone is set on login. Refactors templates and forms to support these changes, and removes the old login template.
This commit is contained in:
49
application/add_meal_v2/templates/add_item.html
Normal file
49
application/add_meal_v2/templates/add_item.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.barcode.label(class="form-label") }}
|
||||
{{ form.barcode(class="form-control-plaintext", readonly=true) }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.name.label(class="form-label") }}
|
||||
{{ form.name(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.energy.label(class="form-label") }}
|
||||
{{ form.energy(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.fat.label(class="form-label") }}
|
||||
{{ form.fat(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.saturated_fat.label(class="form-label") }}
|
||||
{{ form.saturated_fat(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.carbs.label(class="form-label") }}
|
||||
{{ form.carbs(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.sugar.label(class="form-label") }}
|
||||
{{ form.sugar(class="form-control") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.protein.label(class="form-label") }}
|
||||
{{ form.protein(class="form-control") }}
|
||||
</div>
|
||||
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
</form>
|
||||
{% endblock%}
|
||||
134
application/add_meal_v2/templates/scan_barcode.html
Normal file
134
application/add_meal_v2/templates/scan_barcode.html
Normal file
@@ -0,0 +1,134 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="fw-bold">Barcode Scanner</h1>
|
||||
<p class="text-muted">Use your camera to scan barcodes</p>
|
||||
</div>
|
||||
|
||||
<!-- Video preview -->
|
||||
<div class="d-flex justify-content-center mb-4">
|
||||
<video id="video" class="border rounded shadow-sm" style="width: 100%; max-width: 500px;" autoplay
|
||||
muted></video>
|
||||
</div>
|
||||
|
||||
<!-- Start/Stop buttons -->
|
||||
<div class="d-flex justify-content-center gap-3 mb-4">
|
||||
<button id="startButton" class="btn btn-primary px-4">Start Scanning</button>
|
||||
<button id="stopButton" class="btn btn-danger px-4">Stop</button>
|
||||
</div>
|
||||
|
||||
<!-- Search box and suggestions -->
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
<div class="w-100 position-relative" style="max-width: 500px;">
|
||||
<!-- Input group (search + go button) -->
|
||||
<div class="input-group">
|
||||
<input type="text" id="search-box" class="form-control" placeholder="Search..." autocomplete="off">
|
||||
<button id="go-button" class="btn btn-success">Go</button>
|
||||
</div>
|
||||
|
||||
<!-- Suggestions -->
|
||||
<ul id="suggestions" class="list-group position-absolute w-100 mt-1" style="z-index: 1000;"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Suggestions list -->
|
||||
<div class="d-flex justify-content-center">
|
||||
<ul id="suggestions" class="list-group position-absolute mt-1 w-100" style="max-width: 500px; z-index: 1000;">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
const searchBox = document.getElementById('search-box');
|
||||
const suggestionsBox = document.getElementById('suggestions');
|
||||
const goButton = document.getElementById('go-button');
|
||||
|
||||
searchBox.addEventListener('input', function () {
|
||||
const query = searchBox.value;
|
||||
if (query.length < 2) {
|
||||
suggestionsBox.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`{{url_for("add_meal.query")}}?q=${encodeURIComponent(query)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
suggestionsBox.innerHTML = '';
|
||||
data.forEach(item => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = item;
|
||||
|
||||
// Apply Bootstrap classes
|
||||
li.classList.add('list-group-item', 'list-group-item-action', 'cursor-pointer');
|
||||
|
||||
// Add click behavior
|
||||
li.addEventListener('click', () => {
|
||||
searchBox.value = item;
|
||||
suggestionsBox.innerHTML = '';
|
||||
});
|
||||
|
||||
// Add to suggestions box
|
||||
suggestionsBox.appendChild(li);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ Redirect when the "Go" button is clicked
|
||||
goButton.addEventListener('click', () => {
|
||||
const value = searchBox.value.trim();
|
||||
if (value) {
|
||||
const baseURL = "{{url_for('add_meal.step3', input='!')}}";
|
||||
window.location.href = baseURL.replace("!", encodeURIComponent(value));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import { BrowserMultiFormatReader } from 'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/+esm';
|
||||
|
||||
// constants
|
||||
const codeReader = new BrowserMultiFormatReader();
|
||||
const videoElement = document.getElementById('video');
|
||||
|
||||
|
||||
// Start scanning for barcode
|
||||
document.getElementById('startButton').addEventListener('click', async () => {
|
||||
console.log('[DEBUG] Start button clicked')
|
||||
try {
|
||||
await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
} 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 rearCamera = devices.find(device => device.label.toLowerCase().includes('back'))
|
||||
|| devices.find(device => device.label.toLowerCase().includes('rear'))
|
||||
|| devices[0]; // fallback
|
||||
|
||||
const selectedDeviceId = rearCamera?.deviceId;
|
||||
await codeReader.decodeFromVideoDevice(selectedDeviceId, videoElement, async (result, err) => {
|
||||
if (result) {
|
||||
// Result found, this should post the barcode
|
||||
const codeText = result.getText();
|
||||
const baseURL = "{{url_for('add_meal.step3', input='!')}}";
|
||||
window.location.href = baseURL.replace("!", encodeURIComponent(codeText));
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
document.getElementById('stopButton').addEventListener('click', () => {
|
||||
codeReader.reset();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
23
application/add_meal_v2/templates/step4.html
Normal file
23
application/add_meal_v2/templates/step4.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>{{ tod }}</p>
|
||||
<p>{{ item.name }}</p>
|
||||
|
||||
<form method="POST">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-control-plaintext">
|
||||
{{item_id}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
{{ form.amount.label(class="form-label") }}
|
||||
{{ form.amount(class="form-control") }}
|
||||
</div>
|
||||
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
</form>
|
||||
{% endblock%}
|
||||
Reference in New Issue
Block a user