gmao/app_new/templates/dashboard/index.html
root a18e0f2713
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Ajouter les alertes de lots au tableau de bord
2026-08-19 23:46:52 +00:00

715 lines
34 KiB
HTML

{% extends "base.html" %}
{% block title %}Tableau de bord — GMAO Collège{% endblock %}
{% block extra_head %}
<style>
.bg-purple { background-color: #6f42c1 !important; }
.bg-purple.bg-opacity-10 { background-color: rgba(111, 66, 193, 0.1) !important; }
</style>
{% endblock %}
{% block content %}
<h1 class="mb-3 h3"><i class="bi bi-speedometer2"></i> Tableau de bord</h1>
<!-- Alerte modele IA -->
<div id="ai-alert" style="display:none;" class="alert alert-danger mb-3">
<i class="bi bi-exclamation-triangle"></i>
<strong>Modele IA indisponible !</strong>
<span id="ai-alert-msg"></span>
<a href="{{ url_for('ai_config.index') }}" class="alert-link">Configurer le modele</a>
</div>
<!-- Alerte retards -->
<div id="overdue-alert" style="display:none;" class="alert alert-warning mb-3">
<i class="bi bi-clock-history"></i>
<strong>Retards detectes !</strong>
<span id="overdue-alert-msg"></span>
<a href="{{ url_for('scheduler.index') }}" class="alert-link">Voir le planificateur</a>
</div>
<!-- Stats rapides -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-12">
<form method="POST" action="{{ url_for('dashboard.create_month_interventions') }}" class="d-inline" onsubmit="return confirm('Créer les interventions pour le mois prochain?')">
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-calendar-plus me-1"></i>Créer interventions (1 mois)
</button>
</form>
</div>
<div class="col-6 col-md-3">
<div class="card stat-card text-white bg-primary">
<div class="card-body py-2 px-3">
<small class="card-title">Interventions</small>
<h3 class="mb-0">{{ stats.interventions_total }}</h3>
<small>{{ stats.interventions_en_cours }} en cours</small>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card stat-card text-white bg-success">
<div class="card-body py-2 px-3">
<small class="card-title">Curatif / Préventif</small>
<h3 class="mb-0">{{ stats.interventions_curatif }} / {{ stats.interventions_preventif }}</h3>
<small>{{ stats.interventions_recurrentes }} récurrentes</small>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card stat-card text-white bg-warning">
<div class="card-body py-2 px-3">
<small class="card-title">Équipements</small>
<h3 class="mb-0">{{ stats.equipments_total }}</h3>
<small>{{ stats.equipments_hors_service }} hors service</small>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card stat-card {% if stats.parts_low_stock > 0 %}bg-danger{% else %}bg-info{% endif %} text-white">
<div class="card-body py-2 px-3">
<small class="card-title">Stock bas</small>
<h3 class="mb-0">{{ stats.parts_low_stock }}</h3>
<small>pièce(s)</small>
</div>
</div>
</div>
</div>
<!-- Échéances préventif -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-6 col-md-3">
<a href="{{ url_for('interventions_planning.planning') }}" class="text-decoration-none">
<div class="card border-danger h-100">
<div class="card-body py-2 px-3 text-center">
<h3 class="text-danger mb-0">{{ stats.preventif_overdue }}</h3>
<small class="text-muted">En retard</small>
</div>
</div>
</a>
</div>
<div class="col-6 col-md-3">
<a href="{{ url_for('interventions_planning.planning') }}" class="text-decoration-none">
<div class="card border-warning h-100">
<div class="card-body py-2 px-3 text-center">
<h3 class="text-warning mb-0">{{ stats.preventif_this_week }}</h3>
<small class="text-muted">Cette semaine</small>
</div>
</div>
</a>
</div>
<div class="col-6 col-md-3">
<a href="{{ url_for('interventions_planning.planning') }}" class="text-decoration-none">
<div class="card border-info h-100">
<div class="card-body py-2 px-3 text-center">
<h3 class="text-info mb-0">{{ stats.preventif_this_month }}</h3>
<small class="text-muted">Ce mois</small>
</div>
</div>
</a>
</div>
<div class="col-6 col-md-3">
<a href="{{ url_for('interventions.index', type='preventif', frequency='recurring') }}" class="text-decoration-none">
<div class="card border-secondary h-100">
<div class="card-body py-2 px-3 text-center">
<h3 class="text-secondary mb-0">{{ stats.interventions_recurrentes }}</h3>
<small class="text-muted">Récurrentes</small>
</div>
</div>
</a>
</div>
</div>
<!-- Contrôles des référentiels de maintenance -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-lg-6">
<div class="card h-100 {% if stats.lots_without_equipment %}border-warning{% else %}border-success{% endif %}">
<div class="card-header {% if stats.lots_without_equipment %}bg-warning text-dark{% else %}bg-success text-white{% endif %} py-2">
<i class="bi bi-box-seam me-1"></i>Lots présents sans équipement
<span class="badge bg-light text-dark ms-1">{{ stats.lots_without_equipment }}</span>
</div>
<div class="card-body py-2">
{% if lots_without_equipment %}
<div class="list-group list-group-flush">
{% for lot in lots_without_equipment[:8] %}
<a class="list-group-item list-group-item-action px-0 py-1 d-flex justify-content-between" href="{{ url_for('lots.detail', id=lot.id) }}">
<span>{{ lot.name }}</span><i class="bi bi-chevron-right"></i>
</a>
{% endfor %}
</div>
{% if lots_without_equipment|length > 8 %}<small class="text-muted">{{ lots_without_equipment|length - 8 }} autre(s)</small>{% endif %}
{% else %}
<span class="text-success"><i class="bi bi-check-circle me-1"></i>Tous les lots présents ont un équipement.</span>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100 {% if stats.lots_missing_duration %}border-danger{% else %}border-success{% endif %}">
<div class="card-header {% if stats.lots_missing_duration %}bg-danger text-white{% else %}bg-success text-white{% endif %} py-2">
<i class="bi bi-stopwatch me-1"></i>Lots équipés avec durée manquante
<span class="badge bg-light text-dark ms-1">{{ stats.lots_missing_duration }}</span>
</div>
<div class="card-body py-2">
{% if lots_missing_duration %}
<div class="list-group list-group-flush">
{% for item in lots_missing_duration[:8] %}
<div class="list-group-item px-0 py-1">
<div class="d-flex justify-content-between align-items-center">
<a href="{{ url_for('lots.detail', id=item.lot.id) }}"><strong>{{ item.lot.name }}</strong></a>
{% if item.no_task %}<span class="badge bg-danger">Aucune tâche</span>{% else %}<span class="badge bg-danger">{{ item.tasks|length }} tâche(s)</span>{% endif %}
</div>
{% if item.no_task %}
<a class="small text-decoration-none d-block ms-2" href="{{ url_for('lots.detail', id=item.lot.id) }}">
<i class="bi bi-plus-circle me-1"></i>Ajouter une tâche et définir sa durée
</a>
{% endif %}
{% for task in item.tasks[:3] %}
<a class="small text-decoration-none d-block ms-2" href="{{ url_for('lots.task_edit', lot_id=item.lot.id, task_id=task.id) }}">
<i class="bi bi-pencil me-1"></i>{{ task.tache or 'Tâche sans nom' }} — définir la durée
</a>
{% endfor %}
</div>
{% endfor %}
</div>
{% else %}
<span class="text-success"><i class="bi bi-check-circle me-1"></i>Toutes les tâches actives ont une durée définie.</span>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Alertes -->
{% if alerts %}
<div class="card border-warning mb-3">
<div class="card-header bg-warning text-dark py-2">
<i class="bi bi-exclamation-triangle"></i> Alertes récentes
</div>
<div class="card-body p-2">
{% for alert in alerts[:5] %}
<div class="d-flex justify-content-between align-items-center mb-1 p-2 rounded {% if alert.severity == 'critical' %}bg-danger text-white{% elif alert.severity == 'warning' %}bg-warning text-dark{% else %}bg-info text-white{% endif %}" style="font-size:0.85rem;">
<span>{{ alert.message[:100] }}{% if alert.message|length > 100 %}…{% endif %}</span>
</div>
{% endfor %}
{% if alerts|length > 5 %}
<a href="{{ url_for('dashboard.alerts') }}" class="btn btn-sm btn-outline-warning mt-1">Voir tout ({{ alerts|length }})</a>
{% endif %}
</div>
</div>
{% endif %}
<!-- Maintenances du jour -->
{% if day_schedule and day_schedule.items %}
<div class="card border-primary mb-3">
<div class="card-header bg-primary text-white py-2">
<i class="bi bi-calendar-check"></i> Maintenances du jour
<span class="badge bg-light text-primary ms-2">{{ day_schedule.items|length }}</span>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0" style="font-size:0.85rem;">
<thead>
<tr>
<th>Heure</th>
<th>Type</th>
<th>Description</th>
<th>Salle</th>
<th>Durée</th>
</tr>
</thead>
<tbody>
{% for item in day_schedule.items %}
<tr>
<td>
{% if item.start_time %}
{{ item.start_time.strftime('%H:%M') if item.start_time.strftime else item.start_time }}
{% else %}
{% endif %}
</td>
<td>
{% if item.type == 'admin' %}
<span class="badge bg-success">Admin</span>
{% elif item.type == 'curative' %}
<span class="badge bg-danger">Curative</span>
{% elif item.type == 'preventive' %}
<span class="badge bg-primary">Préventive</span>
{% elif item.type == 'formation' %}
<span class="badge bg-warning text-dark">Formation</span>
{% else %}
<span class="badge bg-secondary">{{ item.type }}</span>
{% endif %}
</td>
<td>{{ item.title }}</td>
<td>
{% if item.room_name %}
<span class="badge bg-info">{{ item.room_name }}</span>
{% else %}
{% endif %}
</td>
<td>
{% if item.duration %}
{{ item.duration }} min
{% else %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="p-2 text-center">
<a href="{{ url_for('planning.day', date_str=today.strftime('%Y-%m-%d')) }}" class="btn btn-sm btn-outline-primary">
Voir le planning complet
</a>
</div>
</div>
</div>
{% elif day_schedule and day_schedule.is_working %}
<div class="card border-secondary mb-3">
<div class="card-header bg-secondary text-white py-2">
<i class="bi bi-calendar-check"></i> Maintenances du jour
</div>
<div class="card-body p-3 text-center text-muted">
<i class="bi bi-check-circle fs-1"></i>
<p class="mb-0">Aucune maintenance planifiée pour aujourd'hui.</p>
{% if day_schedule.working_hours %}
<small class="text-muted">Horaires: {{ day_schedule.working_hours.start.strftime('%H:%M') }} - {{ day_schedule.working_hours.end.strftime('%H:%M') }}</small>
{% endif %}
</div>
</div>
{% elif day_schedule and day_schedule.closure %}
<div class="card border-info mb-3">
<div class="card-header bg-info text-white py-2">
<i class="bi bi-calendar-x"></i> {{ day_schedule.closure }}
</div>
<div class="card-body p-3 text-center">
<i class="bi bi-snow fs-1"></i>
<p class="mb-0">Jour non travaillé</p>
</div>
</div>
{% elif day_schedule and day_schedule.leave %}
<div class="card border-warning mb-3">
<div class="card-header bg-warning text-dark py-2">
<i class="bi bi-calendar-x"></i> Congé
</div>
<div class="card-body p-3 text-center">
<p class="mb-0">{{ day_schedule.leave }}</p>
</div>
</div>
{% endif %}
<!-- 2 colonnes : Maintenances + Fréquence -->
<div class="row g-2 g-md-3 mb-3">
<!-- Maintenances à venir -->
<div class="col-12 col-md-6">
<div class="card h-100 border-info">
<div class="card-header bg-info text-white py-2">
<i class="bi bi-calendar-event"></i> Maintenances à venir
</div>
<div class="card-body p-0">
{% if upcoming_maintenances %}
<div class="list-group list-group-flush">
{% for interv in upcoming_maintenances[:10] %}
{% set days_until = (interv.next_due_date - now).days %}
<div class="list-group-item px-3 py-2 {% if days_until < 0 %}list-group-item-danger{% elif days_until < 7 %}list-group-item-warning{% endif %}">
<div class="d-flex justify-content-between align-items-start">
<div class="me-2" style="min-width:0;">
<a href="{{ url_for('interventions.detail', id=interv.id) }}" class="fw-bold text-decoration-none text-truncate d-block">{{ interv.title }}</a>
<small class="text-muted d-block text-truncate">
<i class="bi bi-pc-display"></i> {{ interv.equipment.name }}
{% if interv.frequency_days %}
· <i class="bi bi-arrow-repeat"></i> {{ frequency_labels.get(interv.frequency_days, interv.frequency_days ~ "j") }}
{% endif %}
</small>
</div>
<div class="text-end flex-shrink-0">
{% if days_until < 0 %}
<span class="badge bg-danger">En retard</span>
{% elif days_until < 7 %}
<span class="badge bg-warning text-dark">~{{ days_until }}j</span>
{% else %}
<span class="badge bg-primary">~{{ days_until }}j</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% if upcoming_maintenances|length > 10 %}
<div class="p-2 text-center">
<a href="{{ url_for('interventions_planning.planning') }}" class="btn btn-sm btn-outline-info">Voir tout le planning</a>
</div>
{% endif %}
{% else %}
<div class="p-3 text-muted"><i class="bi bi-check-circle"></i> Aucune maintenance à venir.</div>
{% endif %}
</div>
</div>
</div>
<!-- Répartition fréquence -->
<div class="col-12 col-md-6">
<div class="card h-100 border-secondary">
<div class="card-header bg-secondary text-white py-2">
<i class="bi bi-bar-chart"></i> Répartition par fréquence
</div>
<div class="card-body py-2">
{% if frequency_data %}
{% for item in frequency_data %}
<div class="d-flex justify-content-between align-items-center mb-1" style="font-size:0.85rem;">
<span class="fw-bold me-2">{{ item.label }}</span>
<div class="d-flex align-items-center" style="min-width: 80px;">
<div class="progress flex-grow-1 me-1" style="height: 16px;">
{% set max_count = frequency_data|map(attribute='count')|max %}
{% set pct = (item.count / max_count * 100)|int if max_count > 0 else 0 %}
<div class="progress-bar bg-primary" style="width: {{ pct }}%">{{ item.count }}</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-muted mb-0"><i class="bi bi-info-circle"></i> Aucune donnée.</p>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Workflow + Activité récente -->
<div class="row g-2 g-md-3 mb-3">
<div class="col-12 col-md-6">
<div class="card h-100 border-warning">
<div class="card-header bg-warning text-dark py-2">
<i class="bi bi-hourglass-split"></i> Workflow en attente
</div>
<div class="card-body py-2">
{% if workflow_pending_data %}
{% for item in workflow_pending_data %}
<div class="d-flex justify-content-between align-items-center p-2 mb-1 rounded {% if item.color == 'purple' %}bg-purple bg-opacity-10{% else %}bg-{{ item.color }} bg-opacity-10{% endif %}" style="font-size:0.85rem;">
<div><i class="bi bi-{{ item.icon }}"></i> <span class="fw-bold">{{ item.label }}</span></div>
<span class="badge {% if item.color == 'purple' %}bg-purple{% else %}bg-{{ item.color }}{% endif %}">{{ item.count }}</span>
</div>
{% endfor %}
{% else %}
<p class="text-muted mb-0"><i class="bi bi-check-circle-fill"></i> Rien en attente.</p>
{% endif %}
</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card h-100 border-success">
<div class="card-header bg-success text-white py-2">
<i class="bi bi-activity"></i> Activité récente
</div>
<div class="card-body p-0">
{% if recent_status_changes %}
<div class="list-group list-group-flush">
{% for change in recent_status_changes[:8] %}
<div class="list-group-item px-3 py-2" style="font-size:0.82rem;">
<div class="d-flex justify-content-between">
<div style="min-width:0;">
<small>
<strong>{{ change.changed_by.full_name if change.changed_by else '?' }}</strong>
· <a href="{{ url_for('interventions.detail', id=change.intervention_id) }}">#{{ change.intervention_id }}</a>
</small>
<div class="mt-1">
<span class="badge {% if change.from_color == 'purple' %}bg-purple{% else %}bg-{{ change.from_color }}{% endif %}" style="font-size:0.7rem;">{{ change.from_label }}</span>
<i class="bi bi-arrow-right mx-1" style="font-size:0.6rem;"></i>
<span class="badge {% if change.to_color == 'purple' %}bg-purple{% else %}bg-{{ change.to_color }}{% endif %}" style="font-size:0.7rem;">{{ change.to_label }}</span>
</div>
</div>
<small class="text-muted text-nowrap">{{ change.created_at|datetime_fmt }}</small>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="p-3 text-muted">Aucun changement récent.</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Graphiques -->
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Interventions par mois</h6></div>
<div class="card-body"><canvas id="chart-monthly" height="200"></canvas></div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Interventions par statut</h6></div>
<div class="card-body"><canvas id="chart-status" height="200"></canvas></div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-pie-chart"></i> Equipements par statut</h6></div>
<div class="card-body"><canvas id="chart-equip" height="200"></canvas></div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header"><h6 class="mb-0"><i class="bi bi-bar-chart"></i> Top 5 equipements</h6></div>
<div class="card-body"><canvas id="chart-top" height="200"></canvas></div>
</div>
</div>
</div>
<!-- Dernières interventions -->
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center py-2">
<span><i class="bi bi-clock-history"></i> Dernières interventions</span>
<a href="{{ url_for('interventions.create') }}" class="btn btn-sm btn-primary">
<i class="bi bi-plus-lg"></i> <span class="d-none d-sm-inline">Nouvelle</span>
</a>
</div>
<div class="card-body p-0">
{% if recent_interventions %}
<!-- Desktop : table -->
<div class="table-responsive d-none d-md-block">
<table class="table table-sm table-hover mb-0">
<thead><tr><th>#</th><th>Titre</th><th>Type</th><th>Statut</th><th>Équipement</th></tr></thead>
<tbody>
{% for interv in recent_interventions %}
<tr>
<td><a href="{{ url_for('interventions.detail', id=interv.id) }}">#{{ interv.id }}</a></td>
<td>{{ interv.title[:50] }}{% if interv.title|length > 50 %}…{% endif %}</td>
<td>
{% if interv.type == 'curatif' %}<span class="badge bg-danger">Curatif</span>
{% else %}<span class="badge bg-info">Préventif</span>{% endif %}
</td>
<td><span class="badge {{ interv.status|status_badge }}">{{ interv.status|format_status }}</span></td>
<td>{{ interv.equipment.name[:30] }}{% if interv.equipment.name|length > 30 %}…{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Mobile : cards -->
<div class="d-md-none">
{% for interv in recent_interventions %}
<a href="{{ url_for('interventions.detail', id=interv.id) }}" class="list-group-item list-group-item-action px-3 py-2">
<div class="d-flex justify-content-between align-items-start">
<div style="min-width:0;">
<strong>#{{ interv.id }}</strong> {{ interv.title[:60] }}{% if interv.title|length > 60 %}…{% endif %}
<div class="mt-1">
{% if interv.type == 'curatif' %}<span class="badge bg-danger">Curatif</span>
{% else %}<span class="badge bg-info">Préventif</span>{% endif %}
<span class="badge {{ interv.status|status_badge }}">{{ interv.status|format_status }}</span>
</div>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<div class="p-3 text-muted">Aucune intervention.</div>
{% endif %}
</div>
</div>
<!-- Interprétations en attente -->
{% if pending_interpretations %}
<div class="card mb-3 border-warning">
<div class="card-header bg-warning bg-opacity-10 d-flex justify-content-between align-items-center py-2">
<span><i class="bi bi-robot"></i> Interprétations en attente <span class="badge bg-warning text-dark">{{ pending_interpretations|length }}</span></span>
<a href="{{ url_for('outlook_dashboard.all_interpretations') }}" class="btn btn-sm btn-outline-warning">
Voir tous
</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead><tr><th>Source</th><th>Type</th><th>Expéditeur</th><th>Sujet</th><th>Date</th><th>Actions</th></tr></thead>
<tbody>
{% for interp in pending_interpretations %}
<tr>
<td>
{% if interp.source == 'ENT' %}
<span class="badge bg-info text-dark"><i class="bi bi-broadcast"></i> ENT</span>
{% else %}
<span class="badge bg-primary"><i class="bi bi-microsoft-outlook"></i> Outlook</span>
{% endif %}
</td>
<td>
{% if interp.type == 'curative' %}
<span class="badge bg-danger">Curative</span>
{% elif interp.type == 'preventive' %}
<span class="badge bg-primary">Préventive</span>
{% elif interp.type == 'administrative' %}
<span class="badge bg-warning text-dark">Admin</span>
{% elif interp.type == 'formation' %}
<span class="badge bg-info text-dark">Formation</span>
{% elif interp.type == 'information' %}
<span class="badge bg-secondary">Information</span>
{% else %}
<span class="badge bg-secondary">{{ interp.type or 'N/A' }}</span>
{% endif %}
</td>
<td><small>{{ interp.sender[:20] }}{% if interp.sender|length > 20 %}…{% endif %}</small></td>
<td><small>{{ (interp.subject or 'Sans sujet')[:30] }}{% if interp.subject and interp.subject|length > 30 %}…{% endif %}</small></td>
<td><small>{{ interp.date.strftime('%d/%m %H:%M') if interp.date else '-' }}</small></td>
<td>
<a href="{{ interp.detail_url }}" class="btn btn-sm btn-outline-primary me-1" title="Voir le détail">
<i class="bi bi-eye"></i>
</a>
<a href="{{ interp.create_url }}" class="btn btn-sm btn-success me-1" title="Créer l'intervention">
<i class="bi bi-check-lg"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-danger reject-interp-btn"
data-id="{{ interp.id }}"
data-source="{{ interp.source|lower }}"
title="Refuser">
<i class="bi bi-x-lg"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Gestion du bouton Refuser
document.querySelectorAll('.reject-interp-btn').forEach(btn => {
btn.addEventListener('click', function() {
const interpId = this.dataset.id;
const source = this.dataset.source;
const row = this.closest('tr');
if (!confirm('Refuser cette interprétation et déplacer le message vers Traités ?')) {
return;
}
// Désactiver le bouton
this.disabled = true;
this.innerHTML = '<i class="bi bi-hourglass-split"></i>';
// Appeler l'API de rejet
const url = source === 'ent'
? `/ent/api/interpretation/${interpId}/reject`
: `/outlook/interpretation/${interpId}/reject`;
fetch(url, { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data.success) {
// Supprimer la ligne du tableau
row.style.opacity = '0.5';
row.style.textDecoration = 'line-through';
// Afficher message
const msg = data.message || 'Interprétation rejetée';
alert(msg);
// Recharger la page après 1s
setTimeout(() => location.reload(), 1000);
} else {
alert('Erreur: ' + (data.error || 'Impossible de rejeter'));
this.disabled = false;
this.innerHTML = '<i class="bi bi-x-lg"></i>';
}
})
.catch(err => {
alert('Erreur: ' + err);
this.disabled = false;
this.innerHTML = '<i class="bi bi-x-lg"></i>';
});
});
});
});
</script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('/api/stats').then(r => r.json()).then(data => {
// Interventions par mois
if (data.monthly && data.monthly.length) {
new Chart(document.getElementById('chart-monthly'), {
type: 'bar',
data: {
labels: data.monthly.map(d => d.month),
datasets: [{ label: 'Interventions', data: data.monthly.map(d => d.count), backgroundColor: '#0d6efd' }]
},
options: { responsive: true, plugins: { legend: { display: false } } }
});
}
// Par statut
if (data.by_status && Object.keys(data.by_status).length) {
new Chart(document.getElementById('chart-status'), {
type: 'doughnut',
data: {
labels: Object.keys(data.by_status),
datasets: [{ data: Object.values(data.by_status), backgroundColor: ['#0d6efd','#198754','#ffc107','#dc3545','#6f42c1','#0dcaf0'] }]
},
options: { responsive: true }
});
}
// Equipements par statut
if (data.equip_status && Object.keys(data.equip_status).length) {
new Chart(document.getElementById('chart-equip'), {
type: 'doughnut',
data: {
labels: Object.keys(data.equip_status),
datasets: [{ data: Object.values(data.equip_status), backgroundColor: ['#198754','#dc3545','#ffc107','#6c757d'] }]
},
options: { responsive: true }
});
}
// Top 5 equipements
if (data.top_equipments && data.top_equipments.length) {
new Chart(document.getElementById('chart-top'), {
type: 'bar',
data: {
labels: data.top_equipments.map(d => d.name.substring(0, 20)),
datasets: [{ label: 'Interventions', data: data.top_equipments.map(d => d.count), backgroundColor: '#6f42c1' }]
},
options: { responsive: true, indexAxis: 'y', plugins: { legend: { display: false } } }
});
}
}).catch(err => console.error('Chart data error:', err));
});
</script>
<script>
// Verifier le statut du modele IA au chargement
fetch('/ai-config/api/status').then(r => r.json()).then(d => {
if (d.status === 'error' || d.status === 'no_model' || d.status === 'no_key') {
const alert = document.getElementById('ai-alert');
const msg = document.getElementById('ai-alert-msg');
if (d.status === 'no_key') {
msg.textContent = ' Cle API non configuree.';
} else if (d.status === 'no_model') {
msg.textContent = ' Aucun modele configure.';
} else {
msg.textContent = ' Le modele "' + (d.model || '?') + '" ne repond pas : ' + (d.message || 'erreur');
}
alert.style.display = 'block';
}
}).catch(() => {});
// Verifier les retards
fetch('/scheduler/api/overdue-count').then(r => r.json()).then(d => {
const total = (d.overdue_tasks || 0) + (d.overdue_interventions || 0);
if (total > 0) {
const alert = document.getElementById('overdue-alert');
const msg = document.getElementById('overdue-alert-msg');
msg.textContent = ' ' + total + ' element(s) en retard (' + (d.overdue_tasks || 0) + ' taches, ' + (d.overdue_interventions || 0) + ' interventions).';
alert.style.display = 'block';
}
}).catch(() => {});
</script>
{% endblock %}