78816dfe8c
Punto 14. La segnalazione dell'operatore del 28/07 non era stata circoscritta sul codice: «le dimensioni delle viste cambiano a seconda del menu». Sono quattro meccanismi distinti, tutti leggibili senza avere il tablet in mano. Ogni vista si dichiarava la propria larghezza: sette valori diversi su diciassette template, e nessuno coincideva con quello della navbar, che sta a max-w-7xl su tutte. Il contenuto era quindi disallineato dalla barra sopra di sé, e il disallineamento cambiava a ogni pagina. Il percorso dell'operatore faceva 1280 → 1024 → tutto schermo → 1280 in quattro passaggi. Il padding verticale mescolava py-8 e py-6 fra viste consecutive, così la prima card cambiava altezza a ogni schermata. La barra di scorrimento è classica da 8px e occupa spazio: una pagina lunga la mostrava, una corta no, e la schermata di misura la toglie sempre. A ogni navigazione tutto il contenuto centrato — navbar compresa — si spostava di 8px. La schermata di misura era alta 100vh, che su Android e iOS è l'altezza con la barra dell'indirizzo nascosta: su un tablet il piede, dove stanno «Fine ciclo misura» e il tastierino, finiva sotto il bordo. Ora: .tmf-page e .tmf-page-narrow in themes.css, con i valori esatti della navbar; scrollbar-gutter: stable; 100dvh dove serve. Due larghezze in tutto il prodotto al posto di sette, e il criterio è il tipo di pagina — liste, tabelle e tele stanno larghe, i moduli stanno stretti. Login e schermata di misura tengono la loro geometria, per i motivi scritti in docs/architecture/LAYOUT.md e ripetuti in EXEMPT dentro il test: la prima non ha navbar a cui allinearsi, la seconda non deve scorrere mentre si misura. Il test è statico e guarda i sorgenti: la vista scritta domani copierà la cornice dalla vicina, ed è lì che la deriva ricomincia. Resta da chiedere all'operatore su quale schermo l'ha vista: in verticale su un tablet quasi tutte quelle larghezze collassano e il difetto non si nota. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
115 lines
4.2 KiB
HTML
115 lines
4.2 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ _('Impostazioni') }} - TieMeasureFlow{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="tmf-page tmf-page-narrow"
|
|
x-data="systemSettings()">
|
|
|
|
<!-- Header -->
|
|
<div class="mb-6">
|
|
<h1 class="text-2xl font-bold text-[var(--text-primary)]">{{ _('Impostazioni di Sistema') }}</h1>
|
|
<p class="mt-1 text-sm text-[var(--text-secondary)]">{{ _('Configura i parametri generali del sistema') }}</p>
|
|
</div>
|
|
|
|
<!-- Settings Card -->
|
|
<div class="tmf-card">
|
|
<div class="tmf-card-header flex items-center gap-2">
|
|
<svg class="w-4 h-4 text-[var(--text-muted)]" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
{{ _('Sessione e Sicurezza') }}
|
|
</div>
|
|
|
|
<div class="tmf-card-body space-y-5">
|
|
<!-- Auto-logout -->
|
|
<div>
|
|
<label class="tmf-label" for="auto-logout">{{ _('Auto-logout per inattività (minuti)') }}</label>
|
|
<div class="flex items-center gap-3">
|
|
<input id="auto-logout"
|
|
type="number"
|
|
x-model.number="autoLogoutMinutes"
|
|
class="tmf-input w-32"
|
|
min="1"
|
|
max="480"
|
|
placeholder="—">
|
|
<button @click="autoLogoutMinutes = null"
|
|
class="text-xs text-[var(--text-muted)] hover:text-red-500 transition-colors"
|
|
x-show="autoLogoutMinutes">
|
|
{{ _('Disabilita') }}
|
|
</button>
|
|
</div>
|
|
<p class="text-xs text-[var(--text-muted)] mt-1">
|
|
{{ _('L\'operatore verrà disconnesso automaticamente dopo il periodo di inattività indicato. Lasciare vuoto per disabilitare.') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tmf-card-footer flex items-center justify-between">
|
|
<span x-show="saved" x-transition class="text-sm text-emerald-600 flex items-center gap-1">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
|
</svg>
|
|
{{ _('Salvato') }}
|
|
</span>
|
|
<span x-show="errorMessage" x-transition class="text-sm text-red-600" x-text="errorMessage"></span>
|
|
<div class="flex-1"></div>
|
|
<button @click="save()"
|
|
:disabled="saving"
|
|
class="btn btn-primary gap-1.5">
|
|
<svg x-show="!saving" class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
|
</svg>
|
|
<svg x-show="saving" class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
|
|
</svg>
|
|
{{ _('Salva impostazioni') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
function systemSettings() {
|
|
return {
|
|
autoLogoutMinutes: {{ (settings.auto_logout_minutes or '')|tojson }},
|
|
saving: false,
|
|
saved: false,
|
|
errorMessage: '',
|
|
|
|
async save() {
|
|
this.saving = true;
|
|
this.saved = false;
|
|
this.errorMessage = '';
|
|
|
|
var csrfToken = document.querySelector('meta[name=csrf-token]')?.content;
|
|
var payload = {
|
|
auto_logout_minutes: this.autoLogoutMinutes ? String(this.autoLogoutMinutes) : ''
|
|
};
|
|
|
|
try {
|
|
var resp = await fetch('/admin/api/settings', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken || '' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
var data = await resp.json();
|
|
if (data.error) {
|
|
this.errorMessage = data.detail || data.error;
|
|
} else {
|
|
this.saved = true;
|
|
setTimeout(() => { this.saved = false; }, 3000);
|
|
}
|
|
} catch (err) {
|
|
this.errorMessage = {{ _('Errore di connessione al server')|tojson }};
|
|
}
|
|
this.saving = false;
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|