3c55550d93
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
219 lines
14 KiB
HTML
219 lines
14 KiB
HTML
<!-- 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 %}
|
|
|
|
{% set icon_monitor %}<path stroke-linecap="round" stroke-linejoin="round" d="M9.348 14.652a3.75 3.75 0 010-5.304m5.304 0a3.75 3.75 0 010 5.304m-7.425 2.121a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.807-3.808-9.98 0-13.788m13.788 0c3.808 3.807 3.808 9.98 0 13.788M12 12h.008v.008H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 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 %}
|
|
{# #57: Monitor produzione, visibile ad admin e capoturno (stessa regola di monitor_required) #}
|
|
{% if current_user.get('is_admin') or (current_user.get('roles') and 'Supervisor' in current_user.roles) %}
|
|
{{ nav_link(url_for('monitor.index'), _('Monitor'), icon_monitor, ep.startswith('monitor.'), mobile) }}
|
|
{% endif %}
|
|
{% if current_user.get('is_admin') %}
|
|
{{ 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 }">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex items-center justify-between h-14">
|
|
|
|
<!-- Left: Logo -->
|
|
<div class="flex items-center gap-3">
|
|
<a href="{{ url_for('index') }}"
|
|
class="flex items-center gap-2.5 group text-slate-900 dark:text-white">
|
|
{% if company_logo %}
|
|
<img src="{{ url_for('static', filename='img/' ~ company_logo) }}" alt="Logo" class="h-10 w-auto"
|
|
onerror="this.style.display='none'">
|
|
{% else %}
|
|
{% set logo_class = 'h-9 w-9' %}
|
|
{% set wordmark_class = 'text-xl' %}
|
|
{% set logo_id = 'nav' %}
|
|
{% include 'components/_app_logo.html' %}
|
|
{% endif %}
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Center: Navigation Links (Desktop) -->
|
|
{% if current_user %}
|
|
<div class="hidden md:flex items-center gap-1">
|
|
{{ nav_links() }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Right: Controls -->
|
|
<div class="flex items-center gap-2">
|
|
|
|
<!-- Language Toggle -->
|
|
<div class="flex items-center border border-[var(--border-color)] rounded-lg overflow-hidden">
|
|
{% 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 == lang_code %}
|
|
bg-primary text-white
|
|
{% else %}
|
|
text-[var(--text-secondary)] hover:bg-[var(--bg-secondary)]
|
|
{% endif %}">
|
|
{{ lang_code|upper }}
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Theme Toggle -->
|
|
<button @click="$store.theme.toggle()"
|
|
class="p-2 rounded-lg text-[var(--text-secondary)] hover:text-primary
|
|
hover:bg-[var(--bg-secondary)] transition-colors duration-200"
|
|
:title="$store.theme.dark ? 'Tema chiaro' : 'Tema scuro'">
|
|
<!-- Sun (shown in dark mode) -->
|
|
<svg x-show="$store.theme.dark" x-cloak 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="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
|
|
</svg>
|
|
<!-- Moon (shown in light mode) -->
|
|
<svg x-show="!$store.theme.dark" 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="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- User Menu (when logged in) -->
|
|
{% if current_user %}
|
|
<div class="relative" x-data="{ open: false }" @click.outside="open = false">
|
|
<button @click="open = !open"
|
|
class="flex items-center gap-2 pl-2 pr-3 py-1.5 rounded-lg
|
|
hover:bg-[var(--bg-secondary)] transition-colors duration-200">
|
|
<!-- Avatar -->
|
|
<div class="w-7 h-7 rounded-full bg-primary/10 border border-primary/20
|
|
flex items-center justify-center text-primary font-semibold text-xs">
|
|
{{ current_user.get('display_name', current_user.get('username', '?'))[0]|upper }}
|
|
</div>
|
|
<span class="hidden sm:block text-sm font-medium text-[var(--text-primary)] max-w-[120px] truncate">
|
|
{{ current_user.get('display_name', current_user.get('username', '')) }}
|
|
</span>
|
|
<svg class="w-3.5 h-3.5 text-[var(--text-secondary)] transition-transform duration-200"
|
|
:class="{ 'rotate-180': open }"
|
|
fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- User Dropdown -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-150"
|
|
x-transition:enter-start="opacity-0 scale-95 -translate-y-1"
|
|
x-transition:enter-end="opacity-100 scale-100 translate-y-0"
|
|
x-transition:leave="transition ease-in duration-100"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
class="absolute right-0 mt-1 w-52 rounded-lg bg-[var(--bg-card)] border border-[var(--border-color)]
|
|
shadow-lg py-1 z-50">
|
|
<!-- User Info Header -->
|
|
<div class="px-4 py-2.5 border-b border-[var(--border-color)]">
|
|
<p class="text-sm font-semibold text-[var(--text-primary)] truncate">
|
|
{{ current_user.get('display_name', current_user.get('username', '')) }}
|
|
</p>
|
|
<p class="text-xs text-[var(--text-secondary)] mt-0.5">
|
|
{{ current_user.get('roles', [])|join(', ') }}
|
|
</p>
|
|
</div>
|
|
|
|
<a href="{{ url_for('auth.profile') }}"
|
|
class="flex items-center gap-2.5 px-4 py-2.5 text-sm text-[var(--text-secondary)]
|
|
hover:text-primary hover:bg-primary-50 dark:hover:bg-primary-900/20 transition-colors">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" 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>
|
|
{{ _('Profilo') }}
|
|
</a>
|
|
|
|
<div class="border-t border-[var(--border-color)] my-1"></div>
|
|
|
|
{% if request.endpoint == 'measure.task_execute' %}
|
|
<span class="flex items-center gap-2.5 px-4 py-2.5 text-sm text-[var(--text-muted)] opacity-50 cursor-not-allowed"
|
|
title="{{ _('Logout bloccato durante le misurazioni') }}">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" 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>
|
|
{{ _('Logout') }}
|
|
</span>
|
|
{% else %}
|
|
<form method="post" action="{{ url_for('auth.logout') }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit"
|
|
class="w-full flex items-center gap-2.5 px-4 py-2.5 text-sm text-measure-fail
|
|
hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
|
</svg>
|
|
{{ _('Logout') }}
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Mobile Hamburger (when logged in) -->
|
|
{% if current_user %}
|
|
<button @click="mobileOpen = !mobileOpen"
|
|
class="md:hidden p-2 rounded-lg text-[var(--text-secondary)]
|
|
hover:bg-[var(--bg-secondary)] transition-colors duration-200">
|
|
<svg x-show="!mobileOpen" 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="M4 6h16M4 12h16M4 18h16"/>
|
|
</svg>
|
|
<svg x-show="mobileOpen" x-cloak 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="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
{% endif %}
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Menu -->
|
|
{% if current_user %}
|
|
<div x-show="mobileOpen"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 -translate-y-2"
|
|
x-transition:enter-end="opacity-100 translate-y-0"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100 translate-y-0"
|
|
x-transition:leave-end="opacity-0 -translate-y-2"
|
|
x-cloak
|
|
class="md:hidden border-t border-[var(--border-color)] bg-[var(--bg-card)]">
|
|
<div class="px-4 py-3 space-y-1">
|
|
{{ nav_links(mobile=true) }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</nav>
|