13986f05d7
- New admin blueprint with CRUD proxy endpoints for users - Admin user management template with search, create, edit, toggle active - Navbar: add admin link for is_admin users (desktop + mobile) - Register admin blueprint in app factory - Add IT/EN translations for all admin UI strings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
284 lines
16 KiB
HTML
284 lines
16 KiB
HTML
<!-- TieMeasureFlow Navbar -->
|
|
<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 }">
|
|
<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">
|
|
{% if company_logo %}
|
|
<img src="{{ url_for('static', filename='img/' ~ company_logo) }}" alt="Logo" class="h-8 w-auto"
|
|
onerror="this.src='{{ url_for('static', filename='img/tmflow-logo.svg') }}'">
|
|
{% else %}
|
|
<img src="{{ url_for('static', filename='img/tmflow-logo.svg') }}"
|
|
alt="TieMeasureFlow"
|
|
class="h-8 w-auto">
|
|
{% endif %}
|
|
</a>
|
|
</div>
|
|
|
|
<!-- 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 and request.endpoint.startswith('admin.') %}
|
|
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>
|
|
{% endif %}
|
|
|
|
</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">
|
|
<a href="{{ url_for('set_language', lang='it') }}"
|
|
class="px-2 py-1.5 text-xs font-semibold transition-colors duration-200
|
|
{% if current_language == 'it' %}
|
|
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
|
|
</a>
|
|
</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>
|
|
|
|
<a href="{{ url_for('auth.logout') }}"
|
|
class="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') }}
|
|
</a>
|
|
</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">
|
|
|
|
{% 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 #}
|
|
{% 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>
|
|
{% endif %}
|
|
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</nav>
|