412 lines
24 KiB
HTML
412 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}GMAO Collège{% endblock %}</title>
|
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
|
<link href="{{ url_for('static', filename='css/gmao.css') }}" rel="stylesheet">
|
|
<style>
|
|
/* ── Styles specifiques non couverts par gmao.css ── */
|
|
/* ── Navbar ── */
|
|
.navbar-brand i { font-size: 1.3rem; }
|
|
.navbar .dropdown-divider { border-color: rgba(255,255,255,0.15); }
|
|
.nav-group-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.5); padding: 0.5rem 1rem 0.2rem; }
|
|
@media (min-width: 992px) {
|
|
.nav-group-label { display: none; }
|
|
}
|
|
|
|
/* ── Content ── */
|
|
body { background: #f4f6f9; }
|
|
.main-content { padding: 1rem; }
|
|
@media (min-width: 768px) { .main-content { padding: 1.5rem 2rem; } }
|
|
|
|
/* ── Cards ── */
|
|
.stat-card { transition: transform 0.2s; border-radius: 0.75rem; }
|
|
.stat-card:hover { transform: translateY(-3px); }
|
|
.bg-purple { background-color: #6f42c1 !important; }
|
|
|
|
/* ── Tables responsive ── */
|
|
.table-responsive-stack { width: 100%; }
|
|
@media (max-width: 767px) {
|
|
.table-responsive-stack thead { display: none; }
|
|
.table-responsive-stack tbody tr {
|
|
display: block;
|
|
margin-bottom: 0.75rem;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 0.5rem;
|
|
padding: 0.75rem;
|
|
background: #fff;
|
|
}
|
|
.table-responsive-stack tbody td {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
padding: 0.3rem 0;
|
|
border: none;
|
|
text-align: right;
|
|
}
|
|
.table-responsive-stack tbody td::before {
|
|
content: attr(data-label);
|
|
font-weight: 600;
|
|
text-align: left;
|
|
padding-right: 1rem;
|
|
flex-shrink: 0;
|
|
}
|
|
.table-responsive-stack tbody td:last-child { border-bottom: none; }
|
|
}
|
|
|
|
/* ── Filtres sur mobile ── */
|
|
.filter-bar .row > div { margin-bottom: 0.5rem; }
|
|
|
|
/* ── Workflow bar ── */
|
|
.workflow-step { font-size: 0.8rem; padding: 0.3rem 0.5rem; }
|
|
@media (max-width: 767px) {
|
|
.workflow-steps { flex-wrap: wrap; gap: 0.3rem; }
|
|
.workflow-step { font-size: 0.7rem; }
|
|
}
|
|
|
|
/* ── Planning cards ── */
|
|
.planning-section .badge { font-size: 0.75rem; }
|
|
</style>
|
|
{% block extra_head %}{% endblock %}
|
|
{% if config.get('WTF_CSRF_ENABLED') %}
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
{% endif %}
|
|
</head>
|
|
<body>
|
|
{% if config.get('WTF_CSRF_ENABLED') %}
|
|
<script>
|
|
// Auto-inject CSRF token dans tous les fetch() POST/PUT/DELETE
|
|
(function() {
|
|
const meta = document.querySelector('meta[name="csrf-token"]');
|
|
if (!meta) return;
|
|
const token = meta.getAttribute('content');
|
|
const origFetch = window.fetch;
|
|
window.fetch = function(url, opts) {
|
|
opts = opts || {};
|
|
const method = (opts.method || 'GET').toUpperCase();
|
|
if (method !== 'GET' && method !== 'HEAD') {
|
|
opts.headers = opts.headers || {};
|
|
if (!opts.headers['X-CSRFToken'] && !opts.headers['X-CSRFToken']) {
|
|
opts.headers['X-CSRFToken'] = token;
|
|
}
|
|
}
|
|
return origFetch.call(this, url, opts);
|
|
};
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
|
|
<!-- ── Navbar ── -->
|
|
{% if current_user.is_authenticated %}
|
|
<nav class="navbar navbar-expand-lg navbar-dark" style="background: #2c3e50;">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand fw-bold" href="{{ url_for('dashboard.index') }}">
|
|
<i class="bi bi-tools"></i> <span class="d-none d-sm-inline">GMAO Collège</span>
|
|
</a>
|
|
|
|
<div class="d-flex align-items-center order-lg-last">
|
|
<!-- Alertes badge -->
|
|
{% set unread = stats.alerts_unread if stats is defined else 0 %}
|
|
{% if unread %}
|
|
<a href="{{ url_for('dashboard.alerts') }}" class="btn btn-sm btn-outline-light me-2 position-relative">
|
|
<i class="bi bi-bell"></i>
|
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size:0.6rem;">
|
|
{{ unread }}
|
|
</span>
|
|
</a>
|
|
{% endif %}
|
|
|
|
<!-- Theme toggle -->
|
|
<button class="theme-toggle" id="theme-toggle" title="Basculer le theme">
|
|
<i class="bi bi-moon-fill" id="theme-icon"></i>
|
|
</button>
|
|
|
|
<!-- Notifications -->
|
|
<a href="/logs/" class="btn btn-sm btn-outline-light me-2 position-relative" title="Notifications">
|
|
<i class="bi bi-bell"></i>
|
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" id="notif-count" style="font-size:0.6rem; display:none;">0</span>
|
|
</a>
|
|
|
|
<!-- Utilisateur -->
|
|
{% if current_user.is_authenticated %}
|
|
<div class="dropdown">
|
|
<button class="btn btn-sm btn-outline-light dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
<i class="bi bi-person-circle"></i>
|
|
<span class="d-none d-md-inline ms-1">{{ current_user.full_name }}</span>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li><span class="dropdown-item-text small text-muted">{{ current_user.role_label }}</span></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('auth.change_own_password') }}"><i class="bi bi-key"></i> Changer mon mot de passe</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('auth.logout') }}"><i class="bi bi-box-arrow-right"></i> Déconnexion</a></li>
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Hamburger -->
|
|
<button class="navbar-toggler ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#mainNav">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
<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>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if '/messagerie' in request.path %}active{% endif %}" href="/messagerie">
|
|
<i class="bi bi-envelope-paper"></i> <span>Messagerie</span>
|
|
{% if pending_interpretations_count is defined and pending_interpretations_count > 0 %}
|
|
<span class="badge bg-danger ms-1">{{ pending_interpretations_count }}</span>
|
|
{% endif %}
|
|
</a>
|
|
</li>
|
|
<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">
|
|
<i class="bi bi-wrench-adjustable"></i> <span>Interventions</span>
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
|
<li><a class="dropdown-item" href="{{ url_for('interventions.index') }}"><i class="bi bi-list-ul"></i> Toutes les interventions</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('interventions_planning.planning') }}"><i class="bi bi-calendar3"></i> Planning interventions</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('planning.index') }}"><i class="bi bi-calendar-check"></i> Tableau planning</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><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('planning.meters') }}"><i class="bi bi-speedometer2"></i> Compteurs</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('planning.consumables') }}"><i class="bi bi-box"></i> Consommables</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('planning.availability') }}"><i class="bi bi-clock"></i> Disponibilités</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.rules') }}"><i class="bi bi-shield-check"></i> Règles d'accès</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('trainings.index') }}"><i class="bi bi-mortarboard"></i> Formations</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('trainings.my_trainings') }}"><i class="bi bi-calendar-check"></i> Mes formations</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('exports.export_interventions') }}"><i class="bi bi-file-earmark-pdf"></i> Export interventions</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('scheduler.index') }}"><i class="bi bi-calendar-week"></i> Planificateur auto</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('scheduler.calendar') }}"><i class="bi bi-calendar3"></i> Calendrier</a></li>
|
|
</ul>
|
|
</li>
|
|
<li class="nav-item dropdown">
|
|
<a class="nav-link dropdown-toggle {% if '/equipments' in request.path or '/companies' in request.path or '/parts' in request.path or '/lots' in request.path or '/services' in request.path %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown">
|
|
<i class="bi bi-pc-display"></i> <span>Équipements</span>
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
|
<li><a class="dropdown-item" href="{{ url_for('equipments.index') }}"><i class="bi bi-pc-display"></i> Tous les équipements</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('equipments.categories') }}"><i class="bi bi-tags"></i> Catégories</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<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('rooms.index') }}"><i class="bi bi-door-open"></i> Salles</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('room_types.index') }}"><i class="bi bi-tags"></i> Types de salles</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<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('lots.index') }}"><i class="bi bi-boxes"></i> Lots</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>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('equipments.to_trash') }}"><i class="bi bi-trash"></i> À jeter</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('equipments.trashed') }}"><i class="bi bi-archive"></i> Anciens</a></li>
|
|
</ul>
|
|
</li>
|
|
|
|
{% if current_user.is_admin() %}
|
|
<li class="nav-group-label d-lg-none mt-2">Administration</li>
|
|
<li class="nav-item dropdown">
|
|
<a class="nav-link dropdown-toggle {% if '/gmao-config' in request.path or '/outlook' in request.path or '/ent' in request.path or '/pronote' in request.path or '/yeastar' in request.path or '/status' in request.path or '/logs' in request.path or '/auth/users' in request.path or '/setup-wizard' in request.path %}active{% endif %}" href="#" role="button" data-bs-toggle="dropdown">
|
|
<i class="bi bi-gear"></i> <span>Configuration</span>
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-dark" style="background:#34495e;">
|
|
<li><a class="dropdown-item" href="{{ url_for('gmao_config.index') }}"><i class="bi bi-gear"></i> Contexte GMAO</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('ai_config.index') }}"><i class="bi bi-robot"></i> Configuration IA</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('constraints.index') }}"><i class="bi bi-shield-exclamation"></i> Contraintes de zone</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('outlook_pages.index') }}"><i class="bi bi-envelope-at"></i> Comptes Outlook</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('ent.index') }}"><i class="bi bi-broadcast"></i> ENT77</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('ent.staff') }}"><i class="bi bi-people"></i> Personnels ENT</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('pronote.connect') }}"><i class="bi bi-qr-code"></i> PRONOTE (QR)</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('pronote.sync_salles') }}"><i class="bi bi-download"></i> PRONOTE (sync salles)</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('pronote.planning') }}"><i class="bi bi-calendar3"></i> PRONOTE (plannings)</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('yeastar.index') }}"><i class="bi bi-telephone"></i> Téléphone P520</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('yeastar.config_page') }}"><i class="bi bi-telephone-gear"></i> Config téléphone</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('status.index') }}"><i class="bi bi-activity"></i> Status système</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('logs.index') }}"><i class="bi bi-journal-text"></i> Logs watchdogs</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('chatbot.chatbot_interface') }}"><i class="bi bi-robot"></i> Chatbot</a></li>
|
|
<li><hr class="dropdown-divider" style="border-color:rgba(255,255,255,0.15)"></li>
|
|
<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.data_management') }}"><i class="bi bi-database-exclamation text-danger"></i> Gestion des données</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('setup_wizard.admin_panel') }}"><i class="bi bi-database"></i> Gestion bases</a></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('setup_wizard.index') }}"><i class="bi bi-magic"></i> Setup Wizard</a></li>
|
|
</ul>
|
|
</li>
|
|
<!-- Mobile : liens config à plat -->
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('gmao_config.index') }}"><i class="bi bi-gear"></i> Contexte GMAO</a></li>
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('outlook_pages.index') }}"><i class="bi bi-envelope-at"></i> Outlook</a></li>
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('ent.index') }}"><i class="bi bi-broadcast"></i> ENT77</a></li>
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('pronote.connect') }}"><i class="bi bi-qr-code"></i> PRONOTE</a></li>
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('yeastar.index') }}"><i class="bi bi-telephone"></i> Téléphone</a></li>
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('status.index') }}"><i class="bi bi-activity"></i> Status</a></li>
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('logs.index') }}"><i class="bi bi-journal-text"></i> Logs</a></li>
|
|
<li class="nav-item d-lg-none"><a class="nav-link" href="{{ url_for('auth.list_users') }}"><i class="bi bi-people"></i> Utilisateurs</a></li>
|
|
{% endif %}
|
|
|
|
<li class="nav-group-label d-lg-none mt-2">Alertes</li>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
<!-- ── Flash messages ── -->
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
<div class="container-fluid main-content pb-0">
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
<!-- ── Contenu principal ── -->
|
|
<div class="main-content">
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<!-- ── Overlay de progression ── -->
|
|
<div class="modal fade" id="progressModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
|
<div class="modal-content" style="border:none;background:transparent;">
|
|
<div class="text-center text-white">
|
|
<div class="spinner-border mb-3" role="status" style="width:3rem;height:3rem;">
|
|
<span class="visually-hidden">Chargement…</span>
|
|
</div>
|
|
<p id="progressText" class="fw-semibold" style="text-shadow:0 1px 4px rgba(0,0,0,.7);">Chargement en cours…</p>
|
|
<div class="progress mx-auto" style="max-width:200px;height:6px;">
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width:100%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
/* ── Overlay de progression ──
|
|
Usage JS : showProgress('Texte') / hideProgress()
|
|
Ou sur un <form> : class="show-progress-form" data-progress-text="Texte optionnel"
|
|
*/
|
|
function showProgress(text) {
|
|
var el = document.getElementById('progressText');
|
|
if (el && text) el.textContent = text;
|
|
var modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('progressModal'));
|
|
modal.show();
|
|
}
|
|
function hideProgress() {
|
|
var modal = bootstrap.Modal.getInstance(document.getElementById('progressModal'));
|
|
if (modal) modal.hide();
|
|
}
|
|
/* Auto-show sur les formulaires marqués .show-progress-form */
|
|
/* Si le <form> a data-confirm="message", on fait le confirm() en JS avant d'afficher l'overlay.
|
|
Les anciens onsubmit="return confirm(...)" sont gérés : si le confirm retourne false,
|
|
le submit event ne se déclenche pas, donc l'overlay ne s'affiche pas non plus.
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.querySelectorAll('form.show-progress-form').forEach(function(form) {
|
|
/* Si data-confirm est défini, on remplace le comportement inline */
|
|
if (form.dataset.confirm) {
|
|
form.addEventListener('submit', function(e) {
|
|
if (!confirm(form.dataset.confirm)) {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
var text = form.dataset.progressText || 'Chargement en cours…';
|
|
showProgress(text);
|
|
});
|
|
} else {
|
|
form.addEventListener('submit', function() {
|
|
var text = form.dataset.progressText || 'Chargement en cours…';
|
|
showProgress(text);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
/* Auto-show sur les liens marqués .show-progress-link */
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.querySelectorAll('a.show-progress-link').forEach(function(a) {
|
|
a.addEventListener('click', function() {
|
|
showProgress(a.dataset.progressText || 'Chargement en cours…');
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
// Theme sombre toggle avec memorisation
|
|
(function() {
|
|
const saved = localStorage.getItem('gmao-theme') || 'light';
|
|
if (saved === 'dark') {
|
|
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
|
const icon = document.getElementById('theme-icon');
|
|
if (icon) icon.className = 'bi bi-sun-fill';
|
|
}
|
|
const btn = document.getElementById('theme-toggle');
|
|
if (btn) {
|
|
btn.addEventListener('click', function() {
|
|
const current = document.documentElement.getAttribute('data-bs-theme') || 'light';
|
|
const next = current === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-bs-theme', next);
|
|
localStorage.setItem('gmao-theme', next);
|
|
const icon = document.getElementById('theme-icon');
|
|
if (icon) icon.className = next === 'dark' ? 'bi bi-sun-fill' : 'bi bi-moon-fill';
|
|
});
|
|
}
|
|
})();
|
|
|
|
// Notifications polling (toutes les 60s)
|
|
(function() {
|
|
function pollNotifications() {
|
|
fetch('/api/notifications').then(r => r.json()).then(data => {
|
|
const badge = document.getElementById('notif-count');
|
|
if (badge) {
|
|
badge.textContent = data.count;
|
|
badge.style.display = data.count > 0 ? 'inline' : 'none';
|
|
}
|
|
}).catch(err => console.error('Notif error:', err));
|
|
}
|
|
pollNotifications();
|
|
setInterval(pollNotifications, 60000);
|
|
})();
|
|
</script>
|
|
|
|
{% block extra_scripts %}{% endblock %}
|
|
</body>
|
|
</html>
|