feat(ui): add métier navigation and UX guidance
This commit is contained in:
parent
110723df82
commit
2080f01aa1
8 changed files with 254 additions and 9 deletions
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}{% block title %}Planning des salles{% endblock %}{% block content %}
|
||||
<div class="container-fluid main-content"><div class="d-flex justify-content-between align-items-center mb-3"><div><h1 class="h3">Planning interne des salles</h1><p class="text-muted">Les créneaux restent utilisables sans Pronote. Leur provenance est indiquée pour vous aider à les comprendre.</p></div><a class="btn btn-primary" href="{{ url_for('planning.room_schedule_new') }}"><i class="bi bi-plus-lg"></i> Ajouter un créneau</a></div>
|
||||
<form class="row g-2 mb-3"><div class="col-md-6"><label class="form-label" for="room_id">Salle</label><select id="room_id" name="room_id" class="form-select"><option value="">Choisir une salle</option>{% for room in rooms %}<option value="{{ room.id }}" {% if selected_room_id == room.id %}selected{% endif %}>{{ room.full_name }}</option>{% endfor %}</select></div><div class="col-md-2 align-self-end"><button class="btn btn-outline-primary">Afficher</button></div></form>
|
||||
{% if selected_room_id %}<div class="table-responsive table-responsive-stack"><table class="table"><thead><tr><th>Jour</th><th>Horaires</th><th>Libellé</th><th>Origine</th><th>Protection</th><th>Action</th></tr></thead><tbody>{% for item in schedules %}<tr><td data-label="Jour">{{ item.day_of_week }}</td><td data-label="Horaires">{{ item.start_time }}–{{ item.end_time }}</td><td data-label="Libellé">{{ item.course_name or item.subject or item.event_type }}{% if item.conflict_note %}<br><span class="badge bg-warning text-dark">Conflit à résoudre</span><br><small>{{ item.conflict_note }}</small>{% endif %}</td><td data-label="Origine">{% if item.source == 'pronote' %}Mis à jour par Pronote{% elif item.source == 'import' %}Importé{% else %}Saisi manuellement{% endif %}</td><td data-label="Protection">{% if item.protected_from_sync %}Conservé lors des synchronisations{% else %}Peut être repris par Pronote{% endif %}</td><td data-label="Action"><form method="post" action="{{ url_for('planning.room_schedule_protect', id=item.id) }}" class="d-inline"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><input type="hidden" name="protected_from_sync" value="{% if not item.protected_from_sync %}on{% endif %}"><button class="btn btn-sm btn-outline-secondary">{% if item.protected_from_sync %}Déprotéger{% else %}Protéger{% endif %}</button></form>{% if item.source == 'pronote' %}<form method="post" action="{{ url_for('planning.room_schedule_disable', id=item.id) }}" class="d-inline"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button class="btn btn-sm btn-outline-danger">Désactiver</button></form>{% endif %}</td></tr>{% endfor %}{% if not schedules %}<tr><td colspan="6">Aucun créneau actif pour cette salle.</td></tr>{% endif %}</tbody></table></div>{% endif %}</div>{% endblock %}
|
||||
{% if selected_room_id %}<div class="table-responsive table-responsive-stack"><table class="table"><caption class="visually-hidden">Créneaux d'occupation de la salle sélectionnée</caption><thead><tr><th scope="col">Jour</th><th scope="col">Horaires</th><th scope="col">Libellé</th><th scope="col">Origine</th><th scope="col">Protection</th><th scope="col">Action</th></tr></thead><tbody>{% for item in schedules %}<tr><td data-label="Jour">{{ item.day_of_week }}</td><td data-label="Horaires">{{ item.start_time }}–{{ item.end_time }}</td><td data-label="Libellé">{{ item.course_name or item.subject or item.event_type }}{% if item.conflict_note %}<br><span class="badge bg-warning text-dark">Conflit à résoudre</span><br><small>{{ item.conflict_note }}</small>{% endif %}</td><td data-label="Origine">{% if item.source == 'pronote' %}Mis à jour par Pronote{% elif item.source == 'import' %}Importé{% else %}Saisi manuellement{% endif %}</td><td data-label="Protection">{% if item.protected_from_sync %}Conservé lors des synchronisations{% else %}Peut être repris par Pronote{% endif %}</td><td data-label="Action"><form method="post" action="{{ url_for('planning.room_schedule_protect', id=item.id) }}" class="d-inline"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><input type="hidden" name="protected_from_sync" value="{% if not item.protected_from_sync %}on{% endif %}"><button class="btn btn-sm btn-outline-secondary">{% if item.protected_from_sync %}Déprotéger{% else %}Protéger{% endif %}</button></form>{% if item.source == 'pronote' %}<form method="post" action="{{ url_for('planning.room_schedule_disable', id=item.id) }}" class="d-inline"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button class="btn btn-sm btn-outline-danger">Désactiver</button></form>{% endif %}</td></tr>{% endfor %}{% if not schedules %}<tr><td colspan="6"><div class="py-3 text-muted">Aucun créneau actif pour cette salle. Ajoutez un créneau ou sélectionnez une autre salle.</div></td></tr>{% endif %}</tbody></table></div>{% endif %}</div>{% endblock %}
|
||||
|
|
|
|||
175
app_new/templates/_navigation_phase16.html
Normal file
175
app_new/templates/_navigation_phase16.html
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
<ul class="navbar-nav me-auto mb-2 mb-lg-0" aria-label="Navigation principale">
|
||||
<li class="nav-group-label d-lg-none mt-2">Accueil</li>
|
||||
{% if has_permission('dashboard.view') %}
|
||||
<li class="nav-item"><a class="nav-link {% if request.path == '/' %}active{% endif %}" href="{{ url_for('dashboard.index') }}"><i class="bi bi-speedometer2"></i> Tableau de bord</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% set work_access = has_permission('intervention.view') or has_permission('planning.view') or has_permission('prevention.view') %}
|
||||
{% if work_access %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if '/interventions' in request.path or '/planning' in request.path %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-wrench-adjustable"></i> Mon travail</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
||||
{% if has_permission('intervention.view') %}<li><a class="dropdown-item" href="{{ url_for('interventions.index') }}"><i class="bi bi-list-ul"></i> Interventions</a></li>{% endif %}
|
||||
{% if has_permission('planning.view') %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.room_schedules') }}"><i class="bi bi-door-open"></i> Planning des salles</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.index') }}"><i class="bi bi-calendar-check"></i> Planning unifié</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.tasks') }}"><i class="bi bi-list-check"></i> Tâches préventives</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.scheduled') }}"><i class="bi bi-calendar-week"></i> Tâches planifiées</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.admin_tasks') }}"><i class="bi bi-clipboard"></i> Tâches administratives</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('planning.availability') }}"><i class="bi bi-clock"></i> Disponibilités</a></li>
|
||||
{% endif %}
|
||||
{% if has_permission('prevention.view') %}<li><a class="dropdown-item" href="{{ url_for('prevention.index') }}"><i class="bi bi-shield-check"></i> Prévention</a></li>{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% set heritage_access = has_permission('patrimoine.view') %}
|
||||
{% if heritage_access %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if '/equipments' in request.path or '/buildings' in request.path or '/zones' in request.path or '/rooms' in request.path %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-buildings"></i> Patrimoine</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
||||
<li><a class="dropdown-item" href="{{ url_for('buildings.index') }}"><i class="bi bi-buildings"></i> Bâtiments</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('zones.index') }}"><i class="bi bi-diagram-3"></i> Zones</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('rooms.index') }}"><i class="bi bi-door-open"></i> Salles / locaux</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.index') }}"><i class="bi bi-pc-display"></i> Équipements</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.categories') }}"><i class="bi bi-tags"></i> Catégories</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('room_types.index') }}"><i class="bi bi-tags"></i> Types de salles</a></li>
|
||||
{% if has_permission('housing.private') %}<li><a class="dropdown-item" href="{{ url_for('housing.index') }}"><i class="bi bi-house-door"></i> Logements de fonction</a></li>{% endif %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('equipments.lifecycle_dashboard') }}"><i class="bi bi-activity"></i> Cycle de vie</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if has_permission('stock.view') %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if '/cleaning' in request.path or '/parts' in request.path or '/meters' in request.path %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-boxes"></i> Produits & stocks</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.products') }}"><i class="bi bi-flask"></i> Produits</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.references') }}"><i class="bi bi-upc-scan"></i> Références commerciales</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.stock') }}"><i class="bi bi-boxes"></i> État du stock</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.locations') }}"><i class="bi bi-geo-alt"></i> Emplacements</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.movements') }}"><i class="bi bi-arrow-left-right"></i> Mouvements</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.receive') }}"><i class="bi bi-box-arrow-in-down"></i> Réceptions</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.issue') }}"><i class="bi bi-box-arrow-up"></i> Sorties agents</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.transvasement_page') }}"><i class="bi bi-droplet"></i> Transvasements / dilutions</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.request_profiles') }}"><i class="bi bi-printer"></i> Fiches de demande</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('cleaning.documents') }}"><i class="bi bi-file-earmark-text"></i> FDS et fiches techniques</a></li>
|
||||
{% if has_permission('stock.configure') %}<li><a class="dropdown-item" href="{{ url_for('cleaning.forecast_config') }}"><i class="bi bi-graph-up-arrow"></i> Prévisions</a></li>{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if has_permission('contract.view') %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if '/companies' in request.path or '/contracts' in request.path or '/services' in request.path %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-building"></i> Entreprises</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
||||
<li><a class="dropdown-item" href="{{ url_for('companies.index') }}"><i class="bi bi-building"></i> Entreprises</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('contracts.index') }}"><i class="bi bi-file-earmark-text"></i> Contrats</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('services.index') }}"><i class="bi bi-building-gear"></i> Services</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('parts.index') }}"><i class="bi bi-box-seam"></i> Pièces</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if has_permission('documents.view') %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if request.path.startswith('/documents') or request.path.startswith('/cleaning/documents') %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-folder2-open"></i> Documents</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
||||
<li><a class="dropdown-item" href="{{ url_for('documents.index') }}"><i class="bi bi-search"></i> Centre documentaire</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('documents.fds') }}"><i class="bi bi-file-earmark-medical"></i> FDS accessibles</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if has_permission('system.admin') %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if '/admin/' in request.path or '/auth/users' in request.path or '/status' in request.path or '/logs' in request.path %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"><i class="bi bi-gear"></i> Administration</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
||||
<li><a class="dropdown-item" href="{{ url_for('auth.list_users') }}"><i class="bi bi-people"></i> Utilisateurs</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.roles') }}"><i class="bi bi-shield-lock"></i> Rôles et permissions</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.permissions') }}"><i class="bi bi-key"></i> Catalogue des droits</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('status.index') }}"><i class="bi bi-activity"></i> État du système</a></li>
|
||||
{% if has_permission('watchdog_dnd.view') %}<li><a class="dropdown-item" href="{{ url_for('logs.index') }}"><i class="bi bi-journal-text"></i> Journaux</a></li>{% endif %}
|
||||
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
||||
<li>
|
||||
<details class="px-2 py-1" id="ancien-menu">
|
||||
<summary class="text-light small" style="cursor:pointer"><i class="bi bi-list-columns-reverse"></i> Ancien menu <span class="text-muted">(temporaire)</span></summary>
|
||||
<div class="mt-1" aria-label="Ancien menu complet">
|
||||
<a class="dropdown-item" href="{{ url_for('planning.index') }}">Interventions · Planning unifié</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.room_schedules') }}">Planning des salles</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.tasks') }}">Tâches préventives</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.scheduled') }}">Tâches planifiées</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.meters') }}">Compteurs</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.consumables') }}">Consommables</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.availability') }}">Disponibilités</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.time_tracking') }}">Horaires et suivi annuel</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.admin_tasks') }}">Tâches administratives</a>
|
||||
<a class="dropdown-item" href="{{ url_for('planning.rules') }}">Règles d'accès</a>
|
||||
<a class="dropdown-item" href="{{ url_for('prevention.index') }}">Prévention / DUERP</a>
|
||||
<a class="dropdown-item" href="{{ url_for('trainings.index') }}">Formations</a>
|
||||
<a class="dropdown-item" href="{{ url_for('trainings.my_trainings') }}">Mes formations</a>
|
||||
<a class="dropdown-item" href="{{ url_for('exports.export_interventions') }}">Export interventions</a>
|
||||
<a class="dropdown-item" href="{{ url_for('exports.export_costs_csv') }}">Export coûts et stock</a>
|
||||
<a class="dropdown-item" href="{{ url_for('scheduler.index') }}">Génération automatique</a>
|
||||
<a class="dropdown-item" href="{{ url_for('equipments.index') }}">Tous les équipements</a>
|
||||
<a class="dropdown-item" href="{{ url_for('equipments.categories') }}">Catégories</a>
|
||||
<a class="dropdown-item" href="{{ url_for('buildings.index') }}">Bâtiments</a>
|
||||
<a class="dropdown-item" href="{{ url_for('zones.index') }}">Zones</a>
|
||||
<a class="dropdown-item" href="{{ url_for('rooms.index') }}">Salles</a>
|
||||
<a class="dropdown-item" href="{{ url_for('housing.index') }}">Logements de fonction</a>
|
||||
<a class="dropdown-item" href="{{ url_for('room_types.index') }}">Types de salles</a>
|
||||
<a class="dropdown-item" href="{{ url_for('companies.index') }}">Entreprises</a>
|
||||
<a class="dropdown-item" href="{{ url_for('contracts.index') }}">Contrats</a>
|
||||
<a class="dropdown-item" href="{{ url_for('lots.index') }}">Lots</a>
|
||||
<a class="dropdown-item" href="{{ url_for('services.index') }}">Services</a>
|
||||
<a class="dropdown-item" href="{{ url_for('parts.index') }}">Pièces</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.dashboard') }}">Matériel & Entretien</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.products') }}">Produits génériques</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.references') }}">Références / conditionnements</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.documents') }}">FDS / fiches techniques</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.stock') }}">État du stock</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.receive') }}">Réceptions</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.issue') }}">Sorties agents</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.transvasement_page') }}">Transvasements / dilutions</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.request_profiles') }}">Fiches papier</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.material') }}">Matériel durable</a>
|
||||
<a class="dropdown-item" href="{{ url_for('cleaning.forecast_config') }}">Prévision / permanences</a>
|
||||
<a class="dropdown-item" href="{{ url_for('meters.index') }}">Historique des compteurs</a>
|
||||
<a class="dropdown-item" href="{{ url_for('equipments.to_trash') }}">À jeter</a>
|
||||
<a class="dropdown-item" href="{{ url_for('equipments.lifecycle_dashboard') }}">Cycle de vie</a>
|
||||
<a class="dropdown-item" href="{{ url_for('equipments.trashed') }}">Anciens équipements</a>
|
||||
<a class="dropdown-item" href="{{ url_for('gmao_config.index') }}">Contexte GMAO</a>
|
||||
<a class="dropdown-item" href="{{ url_for('ai_config.index') }}">Configuration IA</a>
|
||||
<a class="dropdown-item" href="{{ url_for('constraints.index') }}">Contraintes de zone</a>
|
||||
<a class="dropdown-item" href="{{ url_for('outlook_pages.index') }}">Comptes Outlook</a>
|
||||
<a class="dropdown-item" href="{{ url_for('outlook_pages.add_account') }}">Ajouter un compte Outlook</a>
|
||||
<a class="dropdown-item" href="{{ url_for('outlook_dashboard.processed_folders_config') }}">Dossiers Outlook surveillés</a>
|
||||
<a class="dropdown-item" href="{{ url_for('ent.index') }}">ENT77</a>
|
||||
<a class="dropdown-item" href="{{ url_for('ent.messages') }}">Messages ENT</a>
|
||||
<a class="dropdown-item" href="{{ url_for('ent.interpretations') }}">Interprétations ENT</a>
|
||||
<a class="dropdown-item" href="{{ url_for('ent.staff') }}">Personnels ENT</a>
|
||||
<a class="dropdown-item" href="{{ url_for('pronote.connect') }}">PRONOTE (QR)</a>
|
||||
<form method="post" action="{{ url_for('pronote.sync_salles') }}"><button class="dropdown-item" type="submit">PRONOTE (synchroniser les salles)</button></form>
|
||||
<a class="dropdown-item" href="{{ url_for('pronote.planning') }}">PRONOTE (plannings)</a>
|
||||
<a class="dropdown-item" href="{{ url_for('pronote.rooms') }}">Salles PRONOTE</a>
|
||||
<a class="dropdown-item" href="{{ url_for('pronote.personnels') }}">Personnels PRONOTE</a>
|
||||
<a class="dropdown-item" href="{{ url_for('pronote.professeurs') }}">Professeurs PRONOTE</a>
|
||||
<a class="dropdown-item" href="{{ url_for('yeastar.index') }}">Téléphone P520</a>
|
||||
<a class="dropdown-item" href="{{ url_for('status.index') }}">Status système</a>
|
||||
<a class="dropdown-item" href="{{ url_for('logs.index') }}">Logs watchdogs</a>
|
||||
<a class="dropdown-item" href="{{ url_for('chatbot.chatbot_interface') }}">Chatbot</a>
|
||||
<a class="dropdown-item" href="{{ url_for('auth.list_users') }}">Utilisateurs</a>
|
||||
<a class="dropdown-item" href="{{ url_for('admin.roles') }}">Rôles et permissions</a>
|
||||
<a class="dropdown-item" href="{{ url_for('admin.data_management') }}">Gestion des données</a>
|
||||
<a class="dropdown-item" href="{{ url_for('admin.template_debug') }}">Audit des templates</a>
|
||||
<a class="dropdown-item" href="{{ url_for('setup_wizard.admin_panel') }}">Gestion bases</a>
|
||||
<a class="dropdown-item" href="{{ url_for('setup_wizard.index') }}">Setup Wizard</a>
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('dashboard.alerts') }}"><i class="bi bi-bell"></i> Alertes{% if unread %} <span class="badge bg-danger ms-1">{{ unread }}</span>{% endif %}</a></li>
|
||||
</ul>
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
Les rôles peuvent cumuler plusieurs permissions. Le code technique reste affiché à titre secondaire pour le diagnostic.
|
||||
Les rôles peuvent cumuler plusieurs permissions. Les administrateurs disposent automatiquement des droits d'administration de leur rôle ; cette page explique les droits attribués sans modifier leur portée.
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
|
|
@ -33,7 +33,8 @@
|
|||
{% else %}
|
||||
<div class="d-flex flex-wrap gap-1 mb-3">
|
||||
{% for link in role.permission_links if link.effect == 'allow' and link.permission.is_active %}
|
||||
<span class="badge text-bg-light border" title="{{ link.permission.description }}">{{ link.permission.name }} <small class="text-muted">({{ link.permission.code }})</small></span>
|
||||
{% set permission_label, permission_description = permission_metadata(link.permission.code) %}
|
||||
<span class="badge text-bg-light border" title="{{ permission_description }}">{{ permission_label }} <small class="text-muted">({{ link.permission.code }})</small></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}{% endif %}
|
||||
|
|
|
|||
|
|
@ -160,9 +160,11 @@
|
|||
|
||||
<!-- Menu collapsible -->
|
||||
<div class="collapse navbar-collapse" id="mainNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
|
||||
<li class="nav-group-label d-lg-none mt-2">Principal</li>
|
||||
{% include "_navigation_phase16.html" %}
|
||||
{% if false %}
|
||||
<!-- Les liens techniques conservés pour compatibilité sont regroupés
|
||||
dans « Ancien menu » et ne sont jamais une barrière de sécurité. -->
|
||||
<!--
|
||||
{% if has_permission('dashboard.view') %}<li class="nav-item">
|
||||
<a class="nav-link {% if request.path == '/' %}active{% endif %}" href="{{ url_for('dashboard.index') }}">
|
||||
<i class="bi bi-speedometer2"></i> <span>Tableau de bord</span>
|
||||
|
|
@ -320,7 +322,8 @@
|
|||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul> -->
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
{% block content %}
|
||||
<h1 class="mb-3 h3"><i class="bi bi-speedometer2"></i> Tableau de bord</h1>
|
||||
<p class="text-muted mb-3">Retrouvez ici les interventions, équipements et stocks qui demandent votre attention. Utilisez le menu pour ouvrir une action ou un référentiel.</p>
|
||||
|
||||
{% if dashboard_permissions.ai %}<!-- Alerte modele IA -->
|
||||
<div id="ai-alert" style="display:none;" class="alert alert-danger mb-3">
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
{% block content %}
|
||||
<div class="container-fluid main-content"><div class="d-flex justify-content-between align-items-center mb-3"><div><h1 class="h3">Fiches de données de sécurité</h1><p class="text-muted">Les FDS sont consultées depuis les documents des références commerciales accessibles.</p></div><a class="btn btn-outline-secondary" href="{{ url_for('documents.index') }}">Centre documentaire</a></div>
|
||||
<form class="row g-2 mb-3"><div class="col-md-8"><label for="q" class="visually-hidden">Rechercher un produit</label><input id="q" name="q" class="form-control" value="{{ filters.get('q','') }}" placeholder="Produit, fabricant ou référence"></div><div class="col-md-2"><button class="btn btn-primary" type="submit">Rechercher</button></div></form>
|
||||
{% if results %}<div class="table-responsive table-responsive-stack"><table class="table table-hover"><thead><tr><th>Produit</th><th>Fabricant/fournisseur</th><th>Fichier</th><th>Version/date</th><th>Emplacements</th><th>Action</th></tr></thead><tbody>{% for item in results %}<tr><td data-label="Produit">{% if item.context_url %}<a href="{{ item.context_url }}">{{ item.context_label }}</a>{% else %}{{ item.context_label }}{% endif %}</td><td data-label="Fabricant/fournisseur">{{ item.issuer or '—' }}</td><td data-label="Fichier">{{ item.filename }}</td><td data-label="Version/date">{{ item.version or '—' }}{% if item.document_date %}<br><small>{{ item.document_date|datetime_fmt }}</small>{% endif %}</td><td data-label="Emplacements">{{ item.location_summary }}</td><td data-label="Action"><a class="btn btn-sm btn-outline-primary" href="{{ item.download_url }}">Télécharger</a></td></tr>{% endfor %}</tbody></table></div>{% else %}<div class="alert alert-info">Aucune FDS accessible n'a été trouvée.</div>{% endif %}</div>
|
||||
{% if results %}<div class="table-responsive table-responsive-stack"><table class="table table-hover"><thead><tr><th>Produit</th><th>Fabricant/fournisseur</th><th>Fichier</th><th>Version/date</th><th>Emplacements</th><th>Action</th></tr></thead><tbody>{% for item in results %}<tr><td data-label="Produit">{% if item.context_url %}<a href="{{ item.context_url }}">{{ item.context_label }}</a>{% else %}{{ item.context_label }}{% endif %}</td><td data-label="Fabricant/fournisseur">{{ item.issuer or '—' }}</td><td data-label="Fichier">{{ item.filename }}</td><td data-label="Version/date">{{ item.version or '—' }}{% if item.document_date %}<br><small>{{ item.document_date|datetime_fmt }}</small>{% endif %}</td><td data-label="Emplacements">{{ item.location_summary }}</td><td data-label="Action"><a class="btn btn-sm btn-outline-primary" href="{{ item.download_url }}"><i class="bi bi-download"></i> Télécharger</a>{% if item.context_url %}<br><a class="btn btn-sm btn-link px-0" href="{{ item.context_url }}">Voir le produit</a>{% endif %}</td></tr>{% endfor %}</tbody></table></div>{% else %}<div class="alert alert-info"><i class="bi bi-info-circle"></i> Aucune FDS accessible n'a été trouvée. Les FDS sont ajoutées depuis les références commerciales des produits.</div>{% endif %}</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<div class="table-responsive table-responsive-stack"><table class="table table-hover align-middle"><thead><tr><th>Document</th><th>Type</th><th>Lié à</th><th>Localisation</th><th>Date</th><th>Actions</th></tr></thead><tbody>
|
||||
{% for item in results %}<tr><td data-label="Document"><strong>{{ item.title }}</strong><br><small class="text-muted">{{ item.filename }}</small></td><td data-label="Type">{{ item.document_type_label }}</td><td data-label="Lié à">{% if item.context_url %}<a href="{{ item.context_url }}">{{ item.context_label }}</a>{% else %}{{ item.context_label }}{% endif %}</td><td data-label="Localisation">{{ item.location_summary }}{% if item.location_labels|length > 1 %}<details><summary class="small">Détail</summary><ul class="mb-0">{% for location in item.location_labels %}<li>{{ location }}</li>{% endfor %}</ul></details>{% endif %}</td><td data-label="Date">{{ item.document_date|datetime_fmt if item.document_date else '—' }}</td><td data-label="Actions"><a class="btn btn-sm btn-outline-primary" href="{{ item.download_url }}"><i class="bi bi-download"></i> Télécharger</a></td></tr>{% endfor %}
|
||||
</tbody></table></div>
|
||||
{% else %}<div class="alert alert-info"><i class="bi bi-info-circle"></i> Aucun document accessible n'a été trouvé. Les documents sont ajoutés depuis les fiches des équipements, interventions et produits.</div>{% endif %}
|
||||
{% else %}<div class="alert alert-info"><i class="bi bi-info-circle"></i> Aucun document accessible n'a été trouvé.<br><span>Les documents sont ajoutés depuis les fiches des équipements, interventions et produits.</span></div>{% endif %}
|
||||
{% if total > per_page %}<nav aria-label="Pagination documentaire"><ul class="pagination"><li class="page-item {% if page <= 1 %}disabled{% endif %}"><a class="page-link" href="{{ url_for('documents.index', page=page-1, **filters.to_dict()) }}">Précédent</a></li><li class="page-item active"><span class="page-link">{{ page }}</span></li><li class="page-item {% if page * per_page >= total %}disabled{% endif %}"><a class="page-link" href="{{ url_for('documents.index', page=page+1, **filters.to_dict()) }}">Suivant</a></li></ul></nav>{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
65
tests/integration/test_phase16_navigation.py
Normal file
65
tests/integration/test_phase16_navigation.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""Couverture de la navigation métier introduite en phase 1.6."""
|
||||
|
||||
from app_new import db
|
||||
from app_new.core.models.rbac import Role, UserRole
|
||||
from app_new.core.models.user import User
|
||||
|
||||
|
||||
def test_admin_sees_new_menu_and_legacy_reference(authenticated_client):
|
||||
response = authenticated_client.get("/auth/profile")
|
||||
assert response.status_code == 200
|
||||
body = response.get_data(as_text=True)
|
||||
for label in ("Mon travail", "Patrimoine", "Produits & stocks", "Documents", "Administration"):
|
||||
assert label in body
|
||||
assert "Ancien menu" in body
|
||||
assert "Référentiel temporaire" in body or "temporaire" in body
|
||||
|
||||
|
||||
def test_non_admin_does_not_see_admin_navigation(client, app):
|
||||
with app.app_context():
|
||||
role = Role.query.filter_by(slug="lecture", is_active=True).first()
|
||||
user = User(username="phase16_lecture", email="phase16_lecture@gmao.local", full_name="Lecture phase 1.6", is_active=True)
|
||||
user.set_password("phase16-lecture-password")
|
||||
db.session.add(user)
|
||||
db.session.flush()
|
||||
db.session.add(UserRole(user_id=user.id, role_id=role.id))
|
||||
db.session.commit()
|
||||
user_id = user.id
|
||||
try:
|
||||
login = client.post("/auth/login", data={"username": "phase16_lecture", "password": "phase16-lecture-password"})
|
||||
assert login.status_code == 302
|
||||
response = client.get("/auth/profile")
|
||||
assert response.status_code == 200
|
||||
body = response.get_data(as_text=True)
|
||||
assert "Administration" not in body
|
||||
assert "Ancien menu" not in body
|
||||
finally:
|
||||
with app.app_context():
|
||||
user = db.session.get(User, user_id)
|
||||
if user:
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def test_new_menu_destinations_do_not_return_accidental_404_or_500(authenticated_client):
|
||||
paths = (
|
||||
"/interventions/",
|
||||
"/planning/rooms",
|
||||
"/planning/",
|
||||
"/equipments/",
|
||||
"/equipments/buildings/",
|
||||
"/equipments/zones/",
|
||||
"/equipments/rooms/",
|
||||
"/cleaning/products",
|
||||
"/cleaning/references",
|
||||
"/cleaning/stock",
|
||||
"/companies/",
|
||||
"/documents/",
|
||||
"/documents/fds",
|
||||
"/admin/users",
|
||||
"/admin/roles",
|
||||
"/admin/permissions",
|
||||
)
|
||||
for path in paths:
|
||||
response = authenticated_client.get(path)
|
||||
assert response.status_code not in (404, 500), path
|
||||
Loading…
Reference in a new issue