mirror of
https://github.com/StefBuwalda/cal_counter.git
synced 2025-10-30 11:19:59 +00:00
Refactor add meal flow and remove v2 routes
Migrated add_meal_v2 routes and templates to add_meal, renaming endpoints and updating references throughout the app. Removed legacy daily_log2 route and template, consolidating to daily_log. Moved macro_arr_to_json to utils.py for reuse. Updated navigation and redirect logic to use new routes. Improved item finding and barcode scanning UI.
This commit is contained in:
49
application/add_meal/templates/add_new_item.html
Normal file
49
application/add_meal/templates/add_new_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%}
|
||||
126
application/add_meal/templates/find_item.html
Normal file
126
application/add_meal/templates/find_item.html
Normal file
@@ -0,0 +1,126 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container d-flex flex-column justify-content-start align-items-center py-4" style="min-height: 100vh;">
|
||||
<div class="card shadow-sm w-100" style="max-width: 480px;">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title text-center mb-4">Item Scanner</h5>
|
||||
<video id="camera" autoplay class="w-100 mb-3" style="aspect-ratio: 4/3;" muted></video>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="manualSearch" class="form-label">Or search manually</label>
|
||||
<input type="text" class="form-control" id="manualSearch" placeholder="Enter item name">
|
||||
</div>
|
||||
|
||||
<ul class="list-group mb-3" id="searchResults"></ul>
|
||||
|
||||
<button class="btn btn-primary w-100" id="createItemBtn">Create New Item</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock%}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const baseURL = "{{url_for('add_meal.select_item', input='!')}}";
|
||||
const baseURL2 = "{{url_for('add_meal.add_new_item', input='!')}}";
|
||||
</script>
|
||||
<script type="module">
|
||||
// Import barcode scanner
|
||||
import { BrowserMultiFormatReader } from 'https://cdn.jsdelivr.net/npm/@zxing/library@0.21.3/+esm';
|
||||
|
||||
// constants
|
||||
const codeReader = new BrowserMultiFormatReader();
|
||||
const videoElement = document.getElementById('camera');
|
||||
|
||||
// Start async camera thingymibob
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('[DEBUG] Page loaded, starting barcode scan');
|
||||
|
||||
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, redirect to the URL
|
||||
console.log(result)
|
||||
const codeText = result.getText();
|
||||
window.location.href = baseURL.replace("!", encodeURIComponent(codeText));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const searchInput = document.getElementById('manualSearch');
|
||||
const resultsList = document.getElementById('searchResults');
|
||||
const createBtn = document.getElementById('createItemBtn');
|
||||
|
||||
let controller; // keeps track of the current fetch
|
||||
|
||||
// TODO: Debounce input, wait for user to stop typing (f.e. 300ms)
|
||||
searchInput.addEventListener('input', () => {
|
||||
const query = searchInput.value.toLowerCase();
|
||||
|
||||
// Check if there is enough input to fetch
|
||||
if (query.length < 2) {
|
||||
resultsList.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller) controller.abort(); // Abort previous fetch if still running
|
||||
controller = new AbortController(); // new controller for this fetch
|
||||
const signal = controller.signal; // Signal to fetch to listen for aborts
|
||||
|
||||
fetch(`{{url_for("add_meal.query")}}?q=${encodeURIComponent(query)}`, { signal })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
resultsList.innerHTML = ''; // clear before appending
|
||||
data.forEach(item => {
|
||||
// Create list item with button inside that changes the hidden form and submits it
|
||||
const li = document.createElement('li');
|
||||
li.className = 'list-group-item p-0';
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'list-group-item list-group-item-action m-0 border-0';
|
||||
btn.style.width = '100%'; // make it fill the li
|
||||
btn.textContent = item;
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
window.location.href = baseURL.replace("!", encodeURIComponent(item));
|
||||
});
|
||||
|
||||
li.appendChild(btn);
|
||||
resultsList.appendChild(li);
|
||||
});
|
||||
}).catch(err => {
|
||||
if (err.name == 'AbortError') {
|
||||
console.log("Fetch aborted");
|
||||
}
|
||||
if (err.name !== 'AbortError') {
|
||||
console.error('Fetch error:', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
createBtn.addEventListener('click', () => {
|
||||
const newItem = searchInput.value.trim()
|
||||
window.location.href = newItem ? baseURL2.replace("!", encodeURIComponent(newItem)) : baseURL2;
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
22
application/add_meal/templates/step4.html
Normal file
22
application/add_meal/templates/step4.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<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