ui: restyle login, dedupe navbar with Jinja macros, touch polish

- login: theme variables instead of hardcoded slate colors, decorative
  brand background, show/hide password toggle, dynamic copyright year
- navbar: nav links rendered via shared nav_link/nav_links macros
  (desktop + mobile from one source), active state now also in mobile
  menu, aria-current on active link, fix nonexistent w-4.5 class
- themes.css: btn :active press feedback, btn-lg variant, card hover
  border accent, brand accent-color and ::selection
- fix tmf-input vs pl-10 cascade conflict (themes.css loads after
  tailwind.css) with important utilities — also fixes recipe search icon
- app.py: inject current_year in template globals
- i18n: add Mostra/Nascondi password (IT/EN)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-11 15:35:53 +00:00
parent 83f8e2d332
commit 821c7c39d5
8 changed files with 209 additions and 275 deletions
@@ -2,17 +2,24 @@
{% block title %}Login — TieMeasureFlow{% endblock %}
{% block content %}
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 px-4 sm:px-6 lg:px-8">
<div class="max-w-md w-full space-y-8">
<div class="relative min-h-screen flex items-center justify-center overflow-hidden bg-[var(--bg-primary)] px-4 sm:px-6 lg:px-8">
<!-- Decorative brand background -->
<div class="pointer-events-none absolute inset-0" aria-hidden="true">
<div class="absolute -top-32 -left-32 w-96 h-96 rounded-full bg-primary-500/10 dark:bg-primary-400/10 blur-3xl"></div>
<div class="absolute -bottom-40 -right-24 w-[28rem] h-[28rem] rounded-full bg-primary-600/10 dark:bg-primary-500/10 blur-3xl"></div>
</div>
<div class="relative max-w-md w-full space-y-6">
<!-- Card -->
<div class="bg-white dark:bg-slate-800 shadow-lg rounded-xl p-8">
<div class="bg-[var(--bg-card)] border border-[var(--border-color)] shadow-lg rounded-2xl p-8">
<!-- Logo -->
<div class="text-center mb-8">
<div class="mx-auto mb-4 inline-flex justify-center text-slate-900 dark:text-white">
{% set logo_class = 'h-16 w-auto' %}
{% include 'components/_app_logo.html' %}
</div>
<p class="text-sm text-slate-600 dark:text-slate-400">
<p class="text-sm text-[var(--text-secondary)]">
{{ _('Accedi al sistema') }}
</p>
</div>
@@ -22,12 +29,12 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- Username -->
<div>
<label for="username" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
<label for="username" class="tmf-label">
{{ _('Username') }}
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="h-5 w-5 text-[var(--text-muted)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
@@ -36,36 +43,52 @@
name="username"
required
autofocus
autocomplete="username"
placeholder="{{ _('Username') }}"
class="block w-full pl-10 pr-3 py-2.5 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-400 dark:focus:border-primary-400 bg-white dark:bg-slate-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 transition-colors">
class="tmf-input !pl-10 !py-2.5">
</div>
</div>
<!-- Password -->
<div>
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
<div x-data="{ show: false }">
<label for="password" class="tmf-label">
{{ _('Password') }}
</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="h-5 w-5 text-[var(--text-muted)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<input type="password"
<input :type="show ? 'text' : 'password'"
type="password"
id="password"
name="password"
required
autocomplete="current-password"
placeholder="{{ _('Password') }}"
class="block w-full pl-10 pr-3 py-2.5 border border-slate-300 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-400 dark:focus:border-primary-400 bg-white dark:bg-slate-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 transition-colors">
class="tmf-input !pl-10 !pr-12 !py-2.5">
<button type="button"
@click="show = !show"
:aria-label="show ? {{ _('Nascondi password')|tojson_attr }} : {{ _('Mostra password')|tojson_attr }}"
class="absolute inset-y-0 right-0 px-3 flex items-center text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors">
<!-- Eye (password hidden, click to show) -->
<svg x-show="!show" class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<!-- Eye slash (password visible, click to hide) -->
<svg x-show="show" x-cloak class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12c1.292 4.338 5.31 7.5 10.066 7.5.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/>
</svg>
</button>
</div>
</div>
<!-- Submit Button -->
<div>
<button type="submit"
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-semibold text-white bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-all duration-200">
<svg class="h-5 w-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<button type="submit" class="btn btn-primary btn-lg w-full shadow-sm">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
</svg>
{{ _('Accedi') }}
@@ -75,7 +98,7 @@
<!-- Help Text -->
<div class="mt-6 text-center">
<p class="text-xs text-slate-500 dark:text-slate-400">
<p class="text-xs text-[var(--text-muted)]">
{{ _('Hai dimenticato la password?') }}
<br>
{{ _('Contatta l\'amministratore') }}
@@ -84,8 +107,8 @@
</div>
<!-- Footer -->
<div class="text-center text-xs text-slate-500 dark:text-slate-400">
<p>TieMeasureFlow &copy; 2025 - {{ _('Sistema di misurazione industriale') }}</p>
<div class="text-center text-xs text-[var(--text-muted)]">
<p>TieMeasureFlow &copy; {{ current_year }} - {{ _('Sistema di misurazione industriale') }}</p>
</div>
</div>
</div>
@@ -1,6 +1,45 @@
<!-- TieMeasureFlow Navbar -->
{# SVG path data for nav icons, shared between desktop and mobile menus #}
{% set icon_measure %}<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>{% endset %}
{% set icon_recipes %}<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>{% endset %}
{% set icon_stats %}<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>{% endset %}
{% set icon_users %}<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>{% endset %}
{% set icon_stations %}<path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25"/>{% endset %}
{% set icon_settings %}<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.204-.107-.397.165-.71.505-.78.929l-.15.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>{% endset %}
{# Single nav link, desktop or mobile variant. `icon` is SVG path markup. #}
{% macro nav_link(href, label, icon, active=false, mobile=false) -%}
<a href="{{ href }}"
{% if active %}aria-current="page"{% endif %}
class="flex items-center {{ 'gap-3 px-3 py-2.5' if mobile else 'gap-2 px-3 py-2' }} rounded-lg text-sm font-medium transition-colors duration-200
{% if active %}text-primary bg-primary-50 dark:bg-primary-900/20{% else %}text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20{% endif %}">
<svg class="{{ 'w-5 h-5' if mobile else 'w-[18px] h-[18px]' }} shrink-0" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">{{ icon }}</svg>
<span>{{ label }}</span>
</a>
{%- endmacro %}
{# Render all role-gated nav links for the current user #}
{% macro nav_links(mobile=false) -%}
{% set ep = request.endpoint or '' %}
{% if current_user.get('roles') and 'MeasurementTec' in current_user.roles %}
{{ nav_link(url_for('measure.select_recipe'), _('Misure'), icon_measure, ep.startswith('measure.'), mobile) }}
{% endif %}
{% if current_user.get('roles') and 'Maker' in current_user.roles %}
{{ nav_link(url_for('maker.recipe_list'), _('Ricette'), icon_recipes, ep.startswith('maker.'), mobile) }}
{% endif %}
{% if current_user.get('roles') and 'Metrologist' in current_user.roles %}
{{ nav_link(url_for('statistics.dashboard'), _('Statistiche'), icon_stats, ep.startswith('statistics.'), mobile) }}
{% endif %}
{% if current_user.get('is_admin') %}
{{ nav_link(url_for('admin.user_list'), _('Utenti'), icon_users, ep == 'admin.user_list', mobile) }}
{{ nav_link(url_for('admin.station_list'), _('Stazioni'), icon_stations, ep == 'admin.station_list', mobile) }}
{{ nav_link(url_for('admin.settings_page'), _('Impostazioni'), icon_settings, ep == 'admin.settings_page', mobile) }}
{% endif %}
{%- endmacro %}
<nav class="sticky top-0 z-40 bg-[var(--bg-card)] border-b border-[var(--border-color)] shadow-sm transition-colors duration-300"
x-data="{ mobileOpen: false, userDropdown: false }">
x-data="{ mobileOpen: false }">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-14">
@@ -21,103 +60,7 @@
<!-- Center: Navigation Links (Desktop) -->
{% if current_user %}
<div class="hidden md:flex items-center gap-1">
{# MeasurementTec: Misure #}
{% if current_user.get('roles') and 'MeasurementTec' in current_user.roles %}
<a href="{{ url_for('measure.select_recipe') }}"
class="nav-link group flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors duration-200
{% if request.endpoint and request.endpoint.startswith('measure.') %}
text-primary bg-primary-50 dark:bg-primary-900/20
{% endif %}">
<!-- Clipboard Icon -->
<svg class="w-4.5 h-4.5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
</svg>
<span>{{ _('Misure') }}</span>
</a>
{% endif %}
{# Maker: Ricette #}
{% if current_user.get('roles') and 'Maker' in current_user.roles %}
<a href="{{ url_for('maker.recipe_list') }}"
class="nav-link group flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors duration-200
{% if request.endpoint and request.endpoint.startswith('maker.') %}
text-primary bg-primary-50 dark:bg-primary-900/20
{% endif %}">
<!-- Edit/Pencil Icon -->
<svg class="w-4.5 h-4.5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
<span>{{ _('Ricette') }}</span>
</a>
{% endif %}
{# Metrologist: Statistiche #}
{% if current_user.get('roles') and 'Metrologist' in current_user.roles %}
<a href="{{ url_for('statistics.dashboard') }}"
class="nav-link group flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors duration-200
{% if request.endpoint and request.endpoint.startswith('statistics.') %}
text-primary bg-primary-50 dark:bg-primary-900/20
{% endif %}">
<!-- Chart Icon -->
<svg class="w-4.5 h-4.5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span>{{ _('Statistiche') }}</span>
</a>
{% endif %}
{# Admin: Utenti #}
{% if current_user.get('is_admin') %}
<a href="{{ url_for('admin.user_list') }}"
class="nav-link group flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors duration-200
{% if request.endpoint == 'admin.user_list' %}
text-primary bg-primary-50 dark:bg-primary-900/20
{% endif %}">
<!-- Users Icon -->
<svg class="w-4.5 h-4.5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
</svg>
<span>{{ _('Utenti') }}</span>
</a>
<a href="{{ url_for('admin.station_list') }}"
class="nav-link group flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors duration-200
{% if request.endpoint == 'admin.station_list' %}
text-primary bg-primary-50 dark:bg-primary-900/20
{% endif %}">
<!-- Station / Workstation Icon -->
<svg class="w-4.5 h-4.5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25"/>
</svg>
<span>{{ _('Stazioni') }}</span>
</a>
<a href="{{ url_for('admin.settings_page') }}"
class="nav-link group flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors duration-200
{% if request.endpoint == 'admin.settings_page' %}
text-primary bg-primary-50 dark:bg-primary-900/20
{% endif %}">
<svg class="w-4.5 h-4.5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.204-.107-.397.165-.71.505-.78.929l-.15.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span>{{ _('Impostazioni') }}</span>
</a>
{% endif %}
{{ nav_links() }}
</div>
{% endif %}
@@ -126,24 +69,17 @@
<!-- Language Toggle -->
<div class="flex items-center border border-[var(--border-color)] rounded-lg overflow-hidden">
<a href="{{ url_for('set_language', lang='it') }}"
{% for lang_code in ['it', 'en'] %}
<a href="{{ url_for('set_language', lang=lang_code) }}"
class="px-2 py-1.5 text-xs font-semibold transition-colors duration-200
{% if current_language == 'it' %}
{% if current_language == lang_code %}
bg-primary text-white
{% else %}
text-[var(--text-secondary)] hover:bg-[var(--bg-secondary)]
{% endif %}">
IT
</a>
<a href="{{ url_for('set_language', lang='en') }}"
class="px-2 py-1.5 text-xs font-semibold transition-colors duration-200
{% if current_language == 'en' %}
bg-primary text-white
{% else %}
text-[var(--text-secondary)] hover:bg-[var(--bg-secondary)]
{% endif %}">
EN
{{ lang_code|upper }}
</a>
{% endfor %}
</div>
<!-- Theme Toggle -->
@@ -265,77 +201,7 @@
x-cloak
class="md:hidden border-t border-[var(--border-color)] bg-[var(--bg-card)]">
<div class="px-4 py-3 space-y-1">
{% if current_user.get('roles') and 'MeasurementTec' in current_user.roles %}
<a href="{{ url_for('measure.select_recipe') }}"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
</svg>
{{ _('Misure') }}
</a>
{% endif %}
{% if current_user.get('roles') and 'Maker' in current_user.roles %}
<a href="{{ url_for('maker.recipe_list') }}"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
{{ _('Ricette') }}
</a>
{% endif %}
{% if current_user.get('roles') and 'Metrologist' in current_user.roles %}
<a href="{{ url_for('statistics.dashboard') }}"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
{{ _('Statistiche') }}
</a>
{% endif %}
{# Admin: Utenti + Stazioni #}
{% if current_user.get('is_admin') %}
<a href="{{ url_for('admin.user_list') }}"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/>
</svg>
{{ _('Utenti') }}
</a>
<a href="{{ url_for('admin.station_list') }}"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25"/>
</svg>
{{ _('Stazioni') }}
</a>
<a href="{{ url_for('admin.settings_page') }}"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium
text-[var(--text-secondary)] hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20
transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.204-.107-.397.165-.71.505-.78.929l-.15.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
{{ _('Impostazioni') }}
</a>
{% endif %}
{{ nav_links(mobile=true) }}
</div>
</div>
{% endif %}
@@ -120,7 +120,7 @@
</div>
<input type="text"
x-model="search"
class="tmf-input pl-10"
class="tmf-input !pl-10"
:placeholder="'{{ _('Cerca per nome, codice o descrizione...') }}'">
</div>
</div>