feat: timer intervallo misura su ricetta + auto-logout per inattività

- Recipe: nuovo campo measurement_interval_minutes (migration 003) con UI nel recipe editor
- Auto-logout: impostazione di sistema configurabile da admin/settings, timer inattività JS
  con warning modale 60s prima del logout, tracking eventi utente throttled
- Navbar: aggiunto link "Impostazioni" per admin (desktop + mobile)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-05-23 14:36:02 +00:00
parent b9c767fb0c
commit 0748ce9b1e
13 changed files with 387 additions and 4 deletions
@@ -231,6 +231,23 @@
rows="3"
placeholder="{{ _('Descrizione opzionale della ricetta...') }}"></textarea>
</div>
<!-- Intervallo di misura -->
<div>
<label class="tmf-label" for="recipe-interval">
{{ _('Intervallo misura (minuti)') }}
</label>
<input id="recipe-interval"
type="number"
x-model.number="measurementIntervalMinutes"
class="tmf-input"
min="1"
max="1440"
placeholder="{{ _('Es. 30') }}">
<p class="text-xs text-[var(--text-muted)] mt-1">
{{ _('Timer cicalino per ricordare la misurazione periodica') }}
</p>
</div>
</div>
</div>
</div>
@@ -466,6 +483,9 @@ function recipeEditor() {
versions: {{ (recipe.versions if recipe and recipe.versions else [])|tojson }},
currentVersion: {{ (recipe.current_version.version_number if recipe and recipe.current_version else 0)|tojson }},
// ---- Measurement interval ----
measurementIntervalMinutes: {{ (recipe.measurement_interval_minutes if recipe and recipe.measurement_interval_minutes else 'null')|tojson }},
// ---- File upload (preview image) ----
currentFilePath: {{ (recipe.image_path if recipe and recipe.image_path else '')|tojson }},
uploadingFile: false,
@@ -487,6 +507,11 @@ function recipeEditor() {
description: this.description.trim(),
};
// Measurement interval
if (this.measurementIntervalMinutes) {
payload.measurement_interval_minutes = parseInt(this.measurementIntervalMinutes, 10);
}
// Include image_path for preview thumbnail
if (this.currentFilePath) {
payload.image_path = this.currentFilePath;