2026-08-14 18:02:25 +02:00
|
|
|
{% extends "base.html" %}
|
|
|
|
|
|
|
|
|
|
{% block title %}Interventions — GMAO{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
|
|
|
<h2 class="mb-0 h4"><i class="bi bi-wrench"></i> Interventions</h2>
|
|
|
|
|
<div class="d-flex gap-2">
|
|
|
|
|
<a href="{{ url_for('interventions.create_for_group_select') }}" class="btn btn-outline-danger btn-sm">
|
|
|
|
|
<i class="bi bi-box-seam"></i> <span class="d-none d-sm-inline">Intervention groupe</span><span class="d-sm-none">Groupe</span>
|
|
|
|
|
</a>
|
|
|
|
|
<a href="{{ url_for('interventions.create') }}" class="btn btn-primary btn-sm">
|
|
|
|
|
<i class="bi bi-plus-circle"></i> <span class="d-none d-sm-inline">Nouvelle</span><span class="d-sm-none">+</span>
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Onglets par statut (scrollable sur mobile) -->
|
|
|
|
|
<ul class="nav nav-pills mb-3 flex-nowrap overflow-auto pb-1" style="font-size:0.85rem;">
|
|
|
|
|
<li class="nav-item flex-shrink-0">
|
|
|
|
|
<a class="nav-link {{ 'active' if not request.args.get('status') else '' }}"
|
|
|
|
|
href="{{ url_for('interventions.index') }}">Toutes</a>
|
|
|
|
|
</li>
|
|
|
|
|
{% for status_key, status_info in statuses.items() %}
|
|
|
|
|
{% if status_counts.get(status_key, 0) > 0 %}
|
|
|
|
|
<li class="nav-item flex-shrink-0">
|
|
|
|
|
<a class="nav-link {{ 'active' if request.args.get('status') == status_key else '' }}"
|
|
|
|
|
href="{{ url_for('interventions.index', status=status_key) }}"
|
|
|
|
|
style="{{ 'background-color: var(--bs-' + status_info['color'] + '); color: white;' if request.args.get('status') == status_key else '' }}">
|
|
|
|
|
{{ status_info['label'] }}
|
|
|
|
|
<span class="badge bg-light text-dark ms-1">{{ status_counts[status_key] }}</span>
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% endfor %}
|
|
|
|
|
{% if trashed_count > 0 or show_trashed %}
|
|
|
|
|
<li class="nav-item flex-shrink-0">
|
|
|
|
|
<a class="nav-link {{ 'active' if show_trashed else '' }}"
|
|
|
|
|
href="{{ url_for('interventions.index', trashed='1') }}"
|
|
|
|
|
style="{{ 'background-color: #dc3545; color: white;' if show_trashed else '' }}">
|
|
|
|
|
<i class="bi bi-trash3"></i> Corbeille
|
|
|
|
|
<span class="badge {{ 'bg-light text-dark' if not show_trashed else 'bg-white text-danger' }} ms-1">{{ trashed_count }}</span>
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
<!-- Filtres (collapsible sur mobile) -->
|
|
|
|
|
<div class="card border-0 shadow-sm mb-3">
|
|
|
|
|
<div class="card-body py-2 filter-bar">
|
|
|
|
|
<!-- Bouton toggle mobile -->
|
|
|
|
|
<div class="d-md-none mb-2">
|
|
|
|
|
<button class="btn btn-sm btn-outline-secondary w-100" type="button" data-bs-toggle="collapse" data-bs-target="#filterCollapse">
|
|
|
|
|
<i class="bi bi-funnel"></i> Filtres
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<form method="GET" class="row g-2 align-items-end collapse d-md-flex" id="filterCollapse">
|
2026-08-21 12:12:23 +02:00
|
|
|
<div class="col-6 col-md-2">
|
|
|
|
|
<select name="workflow_type" class="form-select form-select-sm">
|
|
|
|
|
<option value="">— Workflow —</option>
|
|
|
|
|
{% for key, label in workflow_types.items() %}<option value="{{ key }}" {{ 'selected' if request.args.get('workflow_type') == key }}>{{ label }}</option>{% endfor %}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2026-08-14 18:02:25 +02:00
|
|
|
<div class="col-6 col-md-2">
|
|
|
|
|
<select name="type" class="form-select form-select-sm">
|
|
|
|
|
<option value="">— Type —</option>
|
|
|
|
|
<option value="curatif" {{ 'selected' if request.args.get('type') == 'curatif' }}>Curatif</option>
|
|
|
|
|
<option value="preventif" {{ 'selected' if request.args.get('type') == 'preventif' }}>Préventif</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-6 col-md-2">
|
|
|
|
|
<select name="priority" class="form-select form-select-sm">
|
|
|
|
|
<option value="">— Priorité —</option>
|
|
|
|
|
<option value="urgente" {{ 'selected' if request.args.get('priority') == 'urgente' }}>Urgente</option>
|
|
|
|
|
<option value="haute" {{ 'selected' if request.args.get('priority') == 'haute' }}>Haute</option>
|
|
|
|
|
<option value="normale" {{ 'selected' if request.args.get('priority') == 'normale' }}>Normale</option>
|
|
|
|
|
<option value="basse" {{ 'selected' if request.args.get('priority') == 'basse' }}>Basse</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-6 col-md-2">
|
|
|
|
|
<select name="assigned_to_id" class="form-select form-select-sm">
|
|
|
|
|
<option value="">— Responsable —</option>
|
|
|
|
|
{% for u in users %}
|
|
|
|
|
<option value="{{ u.id }}" {{ 'selected' if request.args.get('assigned_to_id')|string == u.id|string }}>{{ u.full_name }}</option>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-6 col-md-2">
|
|
|
|
|
<select name="frequency" class="form-select form-select-sm">
|
|
|
|
|
<option value="">— Fréquence —</option>
|
|
|
|
|
<option value="recurring" {{ 'selected' if request.args.get('frequency') == 'recurring' }}>Récurrentes</option>
|
|
|
|
|
<option value="ponctuel" {{ 'selected' if request.args.get('frequency') == 'ponctuel' }}>Ponctuelles</option>
|
|
|
|
|
<option value="7" {{ 'selected' if request.args.get('frequency') == '7' }}>Hebdomadaire</option>
|
|
|
|
|
<option value="30" {{ 'selected' if request.args.get('frequency') == '30' }}>Mensuel</option>
|
|
|
|
|
<option value="90" {{ 'selected' if request.args.get('frequency') == '90' }}>Trimestriel</option>
|
|
|
|
|
<option value="365" {{ 'selected' if request.args.get('frequency') == '365' }}>Annuel</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-12 col-md-3">
|
|
|
|
|
<button type="submit" class="btn btn-sm btn-outline-primary"><i class="bi bi-funnel"></i> Filtrer</button>
|
|
|
|
|
<a href="{{ url_for('interventions.index') }}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-x-circle"></i></a>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Liste des interventions -->
|
|
|
|
|
{% if interventions.items %}
|
|
|
|
|
{% for interv in interventions.items %}
|
|
|
|
|
<a href="{{ url_for('interventions.detail', id=interv.id) }}" class="text-decoration-none">
|
|
|
|
|
<div class="card border-0 shadow-sm mb-2 intervention-card">
|
|
|
|
|
<div class="card-body py-2 px-3">
|
|
|
|
|
<div class="d-flex align-items-center gap-1 mb-1 flex-wrap" style="font-size:0.8rem;">
|
|
|
|
|
<span class="badge bg-{{ interv.status_color }}">{{ interv.status_label }}</span>
|
|
|
|
|
<span class="badge bg-{{ interv.priority_color }}">{{ interv.priority_label }}</span>
|
2026-08-21 12:12:23 +02:00
|
|
|
<span class="badge bg-secondary">{{ interv.workflow_label }}</span>
|
2026-08-14 18:02:25 +02:00
|
|
|
{% if interv.type == 'curatif' %}
|
|
|
|
|
<span class="badge bg-danger">Curatif</span>
|
|
|
|
|
{% else %}
|
|
|
|
|
<span class="badge bg-info">Préventif</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if interv.is_group_intervention %}
|
|
|
|
|
<span class="badge bg-warning text-dark"><i class="bi bi-box-seam"></i> Groupe</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if interv.is_recurring and interv.frequency_days %}
|
|
|
|
|
<span class="badge bg-secondary"><i class="bi bi-arrow-repeat"></i></span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if interv.gima_number %}
|
|
|
|
|
<span class="badge bg-info">GIMA</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="fw-bold text-dark">#{{ interv.id }} — {{ interv.title[:80] }}{% if interv.title|length > 80 %}…{% endif %}</div>
|
|
|
|
|
<div class="text-muted" style="font-size:0.8rem;">
|
2026-08-21 12:12:23 +02:00
|
|
|
<i class="bi bi-hdd"></i> {{ interv.equipment.name[:40] if interv.equipment else (interv.room.name if interv.room else 'Localisation générale') }}{% if interv.equipment and interv.equipment.name|length > 40 %}…{% endif %}
|
2026-08-14 18:02:25 +02:00
|
|
|
{% if interv.assigned_to %}
|
|
|
|
|
· <i class="bi bi-person-badge"></i> {{ interv.assigned_to.full_name }}
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if interv.next_due_date and interv.is_recurring %}
|
|
|
|
|
· <i class="bi bi-calendar"></i> {{ interv.next_due_date.strftime('%d/%m/%Y') }}
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if show_trashed and interv.deleted_at %}
|
2026-08-15 00:47:55 +02:00
|
|
|
· <i class="bi bi-trash3"></i> Supprimée le {{ interv.deleted_at|datetime_fmt }}
|
2026-08-14 18:02:25 +02:00
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
{% if show_trashed %}
|
|
|
|
|
<div class="mt-1">
|
|
|
|
|
<form method="POST" action="{{ url_for('interventions.restore', id=interv.id) }}" style="display:inline;">
|
|
|
|
|
<button type="submit" class="btn btn-sm btn-outline-success" onclick="return confirm('Restaurer cette intervention ?')"><i class="bi bi-arrow-counterclockwise"></i> Restaurer</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
|
|
|
|
|
<!-- Pagination -->
|
|
|
|
|
{% if interventions.pages > 1 %}
|
|
|
|
|
<nav class="mt-2">
|
|
|
|
|
<ul class="pagination pagination-sm justify-content-center mb-0">
|
|
|
|
|
{% if interventions.has_prev %}
|
|
|
|
|
<li class="page-item"><a class="page-link" href="{{ url_for('interventions.index', page=interventions.prev_num) }}{% if request.args %}?{{ request.args|urlencode }}{% endif %}">«</a></li>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% for p in range(1, interventions.pages + 1) %}
|
|
|
|
|
{% if p == interventions.page or p <= 2 or p >= interventions.pages - 1 or (p >= interventions.page - 1 and p <= interventions.page + 1) %}
|
|
|
|
|
<li class="page-item {{ 'active' if p == interventions.page else '' }}">
|
|
|
|
|
<a class="page-link" href="{{ url_for('interventions.index', page=p) }}{% if request.args %}?{{ request.args|urlencode }}{% endif %}">{{ p }}</a>
|
|
|
|
|
</li>
|
|
|
|
|
{% elif p == 3 and interventions.page > 4 or p == interventions.pages - 2 and interventions.page < interventions.pages - 3 %}
|
|
|
|
|
<li class="page-item disabled"><span class="page-link">…</span></li>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% endfor %}
|
|
|
|
|
{% if interventions.has_next %}
|
|
|
|
|
<li class="page-item"><a class="page-link" href="{{ url_for('interventions.index', page=interventions.next_num) }}{% if request.args %}?{{ request.args|urlencode }}{% endif %}">»</a></li>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</ul>
|
|
|
|
|
</nav>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
{% else %}
|
|
|
|
|
<div class="text-center py-5 text-muted">
|
|
|
|
|
<i class="bi bi-inbox" style="font-size:3rem;"></i>
|
|
|
|
|
<p class="mt-2">Aucune intervention trouvée</p>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.bg-purple { background-color: #6f42c1 !important; }
|
|
|
|
|
.intervention-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.12) !important; }
|
|
|
|
|
</style>
|
2026-08-21 12:12:23 +02:00
|
|
|
{% endblock %}
|