678 lines
35 KiB
HTML
678 lines
35 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}{{ intervention.title }} — GMAO{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="py-2 py-md-4">
|
||
|
||
{% if intervention.is_deleted %}
|
||
<div class="alert alert-danger mb-3">
|
||
<i class="bi bi-trash3"></i> <strong>Cette intervention est dans la corbeille.</strong>
|
||
Supprimée le {{ intervention.deleted_at|datetime_fmt if intervention.deleted_at else '—' }}.
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if intervention.equipment and intervention.equipment.status in ['a_jeter', 'jete'] %}
|
||
<div class="alert alert-{% if intervention.equipment.status == 'a_jeter' %}danger{% else %}dark{% endif %} mb-3">
|
||
{% if intervention.equipment.status == 'a_jeter' %}
|
||
<i class="bi bi-exclamation-triangle"></i>
|
||
<strong>Équipement à jeter :</strong>
|
||
L'équipement « {{ intervention.equipment.name }} » est marqué pour mise au rebut.
|
||
{% else %}
|
||
<i class="bi bi-archive"></i>
|
||
<strong>Équipement mis au rebut :</strong>
|
||
L'équipement « {{ intervention.equipment.name }} » a été définitivement mis au rebut. L'historique reste consultable.
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- En-tête -->
|
||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start mb-3 gap-2">
|
||
<div>
|
||
<h2 class="mb-1 h5">
|
||
<span class="badge bg-{% if intervention.type == 'curatif' %}danger{% else %}info{% endif %}">
|
||
{% if intervention.type == 'curatif' %}🔧 Curatif{% else %}🛡️ Préventif{% endif %}
|
||
</span>
|
||
{{ intervention.title }}
|
||
</h2>
|
||
<span class="text-muted" style="font-size:0.85rem;">Intervention #{{ intervention.id }} — {{ intervention.created_at|datetime_fmt if intervention.created_at else '—' }}</span>
|
||
</div>
|
||
<div class="d-flex gap-2 align-items-center">
|
||
<span class="badge bg-{{ intervention.status_color }}" style="font-size:1rem;">
|
||
{{ intervention.status_label }}
|
||
</span>
|
||
<span class="badge bg-{{ intervention.priority_color }}">
|
||
{{ intervention.priority_label }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ Barre de workflow ═══ -->
|
||
<div class="card border-0 shadow-sm mb-3 mb-md-4 bg-white">
|
||
<div class="card-body py-2 py-md-3">
|
||
<h6 class="text-muted mb-2" style="font-size:0.85rem;"><i class="bi bi-diagram-3"></i> Workflow</h6>
|
||
<form method="POST" action="{{ url_for('interventions.save_work_details', id=intervention.id) }}" class="d-flex flex-wrap gap-2 align-items-center mb-2">
|
||
<label class="small text-muted mb-0">Parcours :</label>
|
||
<select name="execution_mode" class="form-select form-select-sm" style="max-width:280px;">
|
||
{% for value, label in [('interne', 'Intervention interne'), ('chef', 'Validation du chef'), ('departement', 'Demande au Département'), ('entreprise', 'Entreprise extérieure')] %}
|
||
<option value="{{ value }}" {% if intervention.execution_mode == value %}selected{% endif %}>{{ label }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<button class="btn btn-outline-primary btn-sm" type="submit"><i class="bi bi-arrow-repeat"></i> Appliquer</button>
|
||
</form>
|
||
<div class="d-flex flex-wrap align-items-center gap-1" style="font-size:0.75rem;">
|
||
{% set workflow_order = workflow_status_order %}
|
||
<div class="mb-2"><span class="badge bg-secondary">Workflow {{ intervention.workflow_label }}</span></div>
|
||
{% for step in workflow_order %}
|
||
{% if step in statuses %}
|
||
{% set is_current = (step == intervention.status) %}
|
||
{% set is_past = workflow_order.index(step) < workflow_order.index(intervention.status) if intervention.status in workflow_order else false %}
|
||
{% set bg_class = 'bg-' + statuses[step]['color'] if is_current else ('bg-success' if is_past else 'bg-light') %}
|
||
{% set text_class = 'text-white' if is_current or is_past else 'text-muted' %}
|
||
<span class="badge {{ bg_class }} {{ text_class }} py-1 px-2" {% if is_current %}style="box-shadow: 0 0 0 3px rgba(13,110,253,0.3);"{% endif %}>
|
||
{% if is_past %}<i class="bi bi-check2 me-1"></i>{% endif %}
|
||
{{ statuses[step]['label'] }}
|
||
</span>
|
||
{% if not loop.last %}<i class="bi bi-chevron-right text-muted d-none d-md-inline mx-1"></i><i class="bi bi-chevron-down text-muted d-md-none"></i>{% endif %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ Actions rapides ═══ -->
|
||
{% if transitions %}
|
||
<div class="card border-0 shadow-sm mb-3 mb-md-4 border-start border-primary border-4">
|
||
<div class="card-body">
|
||
<h6 class="mb-3"><i class="bi bi-lightning-fill text-warning"></i> Actions rapides</h6>
|
||
<form method="POST" action="{{ url_for('interventions.change_status', id=intervention.id) }}">
|
||
<div class="mb-2">
|
||
<label class="form-label small text-muted">Changer le statut vers :</label>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
{% for status, label in transitions %}
|
||
<button type="submit" name="to_status" value="{{ status }}" class="btn btn-outline-primary btn-sm">
|
||
{{ label }}
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<div class="d-flex gap-2 align-items-center">
|
||
<input type="text" name="comment" class="form-control form-control-sm" placeholder="Commentaire (optionnel)...">
|
||
<button type="submit" class="btn btn-primary btn-sm">
|
||
<i class="bi bi-check-circle"></i> Confirmer
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
{% if intervention.status not in ['terminee', 'cloturee', 'refusee', 'annulee'] %}
|
||
<hr class="my-3">
|
||
<form method="POST" action="{{ url_for('interventions.refuse', id=intervention.id) }}" onsubmit="return confirm('Refuser cette intervention ? Le motif sera conservé dans l\'historique.')">
|
||
<label class="form-label small text-muted">Refuser la demande</label>
|
||
<div class="input-group input-group-sm">
|
||
<input type="text" name="refusal_reason" class="form-control" required minlength="3" placeholder="Motif obligatoire (ex. demande non pertinente)...">
|
||
<button type="submit" class="btn btn-outline-danger"><i class="bi bi-ban"></i> Refuser</button>
|
||
</div>
|
||
</form>
|
||
{% endif %}
|
||
|
||
{% if intervention.equipment and intervention.equipment.status not in ['a_jeter', 'jete'] %}
|
||
<hr class="my-3">
|
||
<form method="POST" action="{{ url_for('interventions.mark_equipment_trash', id=intervention.id) }}">
|
||
<div class="mb-2">
|
||
<label class="form-label small text-muted">Action spéciale :</label>
|
||
</div>
|
||
<div class="d-flex gap-2 align-items-center">
|
||
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Marquer l\'équipement comme à jeter et clôturer cette intervention ?')">
|
||
<i class="bi bi-trash"></i> Marquer l'équipement à jeter et clôturer
|
||
</button>
|
||
</div>
|
||
</form>
|
||
{% endif %}
|
||
|
||
{% if intervention.status == 'refusee' %}
|
||
<div class="alert alert-danger mb-3">
|
||
<h6 class="alert-heading"><i class="bi bi-ban"></i> Intervention refusée</h6>
|
||
<div>{{ intervention.refusal_reason or 'Motif non renseigné' }}</div>
|
||
<small>Le {{ intervention.refused_at|datetime_fmt if intervention.refused_at else '—' }}{% if intervention.refused_by %} par {{ intervention.refused_by.full_name }}{% endif %}</small>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<div class="alert alert-secondary mb-3 mb-md-4">
|
||
<i class="bi bi-lock"></i> Aucune transition possible depuis le statut actuel avec votre rôle.
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="row g-4">
|
||
|
||
<!-- ═══ Colonne gauche : infos principales ═══ -->
|
||
<div class="col-lg-8">
|
||
|
||
<!-- Responsable actuel -->
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-person-badge text-primary"></i> Responsable actuel</h6>
|
||
{% if intervention.assigned_to %}
|
||
<div class="d-flex align-items-center">
|
||
<span class="avatar-placeholder bg-primary text-white rounded-circle d-flex align-items-center justify-content-center me-2" style="width:40px;height:40px;font-size:1.1rem;">
|
||
{{ intervention.assigned_to.full_name[0]|upper }}
|
||
</span>
|
||
<div>
|
||
<strong>{{ intervention.assigned_to.full_name }}</strong><br>
|
||
<span class="badge bg-secondary">{{ intervention.assigned_to.role_label }}</span>
|
||
</div>
|
||
</div>
|
||
{% elif intervention.status == 'brouillon' %}
|
||
<span class="text-muted"><i class="bi bi-pencil"></i> Brouillon — pas encore de responsable assigné</span>
|
||
{% else %}
|
||
<span class="text-muted"><i class="bi bi-question-circle"></i> Non assigné</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Technicien assigné -->
|
||
{% if intervention.technician %}
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-wrench text-success"></i> Technicien assigné</h6>
|
||
<strong>{{ intervention.technician.full_name }}</strong>
|
||
<span class="badge bg-success ms-2">{{ intervention.technician.role_label }}</span>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Description -->
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-file-text text-info"></i> Description</h6>
|
||
{% if intervention.description %}
|
||
<p class="mb-0" style="white-space: pre-wrap;">{{ intervention.description }}</p>
|
||
{% else %}
|
||
<p class="text-muted mb-0 fst-italic">Aucune description</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- GIMA & Budget -->
|
||
{% if intervention.gima_number or intervention.needs_budget %}
|
||
<div class="card border-0 shadow-sm mb-3 border-start border-info border-4">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-building text-info"></i> GIMA & Budget</h6>
|
||
<div class="row">
|
||
{% if intervention.gima_number %}
|
||
<div class="col-sm-6">
|
||
<strong>N° demande GIMA :</strong> {{ intervention.gima_number }}<br>
|
||
<strong>Service :</strong>
|
||
{% if intervention.gima_service == 'entretien' %}
|
||
Service Entretien
|
||
{% elif intervention.gima_service == 'securite' %}
|
||
Service Sécurité
|
||
{% else %}
|
||
{{ intervention.gima_service or '—' }}
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
{% if intervention.needs_budget %}
|
||
<div class="col-sm-6">
|
||
<span class="badge bg-purple"><i class="bi bi-cash-coin"></i> Budget prévisionnel requis</span><br>
|
||
{% if intervention.budget_amount %}
|
||
<strong>Montant :</strong> {{ "%.2f"|format(intervention.budget_amount) }} €
|
||
{% endif %}
|
||
{% if intervention.budget_approved %}
|
||
<span class="badge bg-success ms-1">Approuvé</span>
|
||
{% else %}
|
||
<span class="badge bg-warning text-dark ms-1">En attente</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Workflow travaux intégré -->
|
||
<div class="card border-0 shadow-sm mb-3 border-start border-warning border-4">
|
||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||
<h6 class="mb-0"><i class="bi bi-hammer text-warning"></i> Suivi des travaux</h6>
|
||
<span class="badge bg-secondary">Dans cette intervention</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<form method="POST" action="{{ url_for('interventions.save_work_details', id=intervention.id) }}">
|
||
<div class="row g-2">
|
||
<div class="col-md-4">
|
||
<label class="form-label small">Mode de réalisation</label>
|
||
<select name="execution_mode" class="form-select form-select-sm">
|
||
{% for value, label in [('interne', 'Travaux réalisés par moi-même'), ('chef', 'Validation de mon chef'), ('departement', 'Demande au Département'), ('entreprise', 'Entreprise extérieure')] %}
|
||
<option value="{{ value }}" {% if intervention.execution_mode == value %}selected{% endif %}>{{ label }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small">Service du Département</label>
|
||
<select name="department_service_id" class="form-select form-select-sm">
|
||
<option value="">— Non concerné —</option>
|
||
{% for service in services %}<option value="{{ service.id }}" {% if intervention.department_service_id == service.id %}selected{% endif %}>{{ service.name }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small">Entreprise mandatée</label>
|
||
<select name="company_id" class="form-select form-select-sm">
|
||
<option value="">— Aucune —</option>
|
||
{% for company in companies %}<option value="{{ company.id }}" {% if intervention.company_id == company.id %}selected{% endif %}>{{ company.name }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4"><label class="form-label small">État du devis</label><select name="quote_status" class="form-select form-select-sm">{% for value,label in [('non_requis','Non requis'),('attendu','Attendu'),('recu','Reçu'),('valide_chef','Validé par le chef'),('valide_departement','Validé par le Département'),('refuse','Refusé')] %}<option value="{{ value }}" {% if intervention.quote_status == value %}selected{% endif %}>{{ label }}</option>{% endfor %}</select></div>
|
||
<div class="col-md-4"><label class="form-label small">Référence devis</label><input name="quote_reference" class="form-control form-control-sm" value="{{ intervention.quote_reference or '' }}"></div>
|
||
<div class="col-md-2"><label class="form-label small">Montant HT</label><input type="number" step="0.01" name="quote_amount_ht" class="form-control form-control-sm" value="{{ intervention.quote_amount_ht or '' }}"></div>
|
||
<div class="col-md-2"><label class="form-label small">Montant TTC</label><input type="number" step="0.01" name="quote_amount_ttc" class="form-control form-control-sm" value="{{ intervention.quote_amount_ttc or '' }}"></div>
|
||
<div class="col-md-4"><label class="form-label small">Date demande Département</label><input type="date" name="department_request_date" class="form-control form-control-sm" value="{{ intervention.department_request_date or '' }}"></div>
|
||
<div class="col-md-4"><label class="form-label small">Date validation chef</label><input type="date" name="chief_validation_date" class="form-control form-control-sm" value="{{ intervention.chief_validation_date or '' }}"></div>
|
||
<div class="col-md-4"><label class="form-label small">Date validation Département</label><input type="date" name="department_validation_date" class="form-control form-control-sm" value="{{ intervention.department_validation_date or '' }}"></div>
|
||
<div class="col-12"><label class="form-label small">Notes de suivi</label><textarea name="work_notes" class="form-control form-control-sm" rows="2">{{ intervention.work_notes or '' }}</textarea></div>
|
||
</div>
|
||
<button class="btn btn-warning btn-sm mt-3"><i class="bi bi-save"></i> Enregistrer le suivi</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Équipement -->
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-hdd text-warning"></i> Équipement</h6>
|
||
<div class="mb-2"><strong>Salle :</strong> {% if intervention.room %}{{ intervention.room.full_name }}{% elif intervention.equipment and intervention.equipment.room %}{{ intervention.equipment.room.full_name }}{% else %}<span class="text-muted">Non renseignée</span>{% endif %}</div>
|
||
{% if intervention.is_group_intervention %}
|
||
<div class="mb-2">
|
||
<span class="badge bg-info">Groupe d'équipements</span>
|
||
<span class="badge bg-secondary ms-1">{{ intervention.quantity_affected }} unité(s) concernée(s)</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if intervention.equipment %}
|
||
<a href="{{ url_for('equipments.detail', id=intervention.equipment.id) }}" class="text-decoration-none">
|
||
{{ intervention.equipment.code or intervention.equipment.name }}
|
||
</a>
|
||
<br><span class="text-muted small">{{ intervention.equipment.full_path }}</span>
|
||
{% else %}
|
||
<span class="text-muted">Aucun équipement précis associé</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Localisation modifiable en cascade -->
|
||
<div class="card border-0 shadow-sm mb-3 border-start border-success border-4">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-geo-alt text-success"></i> Salle et équipement concernés</h6>
|
||
<form method="POST" action="{{ url_for('interventions.update_location', id=intervention.id) }}" id="intervention-location-form">
|
||
<div class="row g-2">
|
||
<div class="col-md-4">
|
||
<label class="form-label small">1. Salle</label>
|
||
<select name="room_id" id="intervention-room" class="form-select form-select-sm">
|
||
<option value="">— Aucune salle —</option>
|
||
{% for room in rooms %}<option value="{{ room.id }}" {% if intervention.room_id == room.id %}selected{% endif %}>{{ room.full_name }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small">2. Type d’équipement</label>
|
||
<select name="category_id" id="intervention-category" class="form-select form-select-sm">
|
||
<option value="">— Toutes les catégories —</option>
|
||
{% for category in categories %}<option value="{{ category.id }}" {% if intervention.equipment and intervention.equipment.effective_category_id == category.id %}selected{% endif %}>{{ category.name }}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label small">3. Équipement précis</label>
|
||
<select name="equipment_id" id="intervention-equipment" class="form-select form-select-sm">
|
||
<option value="">— Aucun équipement précis —</option>
|
||
{% for equipment in equipment_choices %}<option value="{{ equipment.id }}" data-room="{{ equipment.room_id or '' }}" data-category="{{ equipment.effective_category_id or '' }}" {% if intervention.equipment_id == equipment.id %}selected{% endif %}>{{ equipment.name }}{% if equipment.code %} ({{ equipment.code }}){% endif %}</option>{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<button type="submit" class="btn btn-outline-success btn-sm mt-3"><i class="bi bi-save"></i> Enregistrer la localisation</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Éléments du groupe concernés -->
|
||
{% if intervention.is_group_intervention %}
|
||
{% set affected_units = intervention.get_affected_units() %}
|
||
{% if affected_units %}
|
||
<div class="card border-0 shadow-sm mb-3 border-start border-warning border-4">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-list-check text-warning"></i> Éléments concernés ({{ affected_units|length }})</h6>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-hover mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th>N°</th>
|
||
<th>Code</th>
|
||
<th>Position</th>
|
||
<th>Statut</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for unit in affected_units %}
|
||
<tr>
|
||
<td>{{ unit.individual_number }}</td>
|
||
<td><a href="{{ url_for('equipments.detail', id=unit.id) }}">{{ unit.code or '—' }}</a></td>
|
||
<td>{{ unit.position or '—' }}</td>
|
||
<td>
|
||
<span class="badge {{ unit.status|status_badge }}">{{ unit.status|format_status }}</span>
|
||
{% if unit.has_active_curative and unit.id != intervention.equipment_id %}
|
||
<span class="badge bg-warning text-dark ms-1" title="Intervention curative en cours">
|
||
<i class="bi bi-wrench"></i>
|
||
</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if intervention.status not in ['cloturee', 'terminee'] %}
|
||
<div class="btn-group btn-group-sm">
|
||
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
||
Statut
|
||
</button>
|
||
<ul class="dropdown-menu">
|
||
{% for value, label in [('en_service', 'En service'), ('hs', 'Hors service'), ('a_jeter', 'À jeter')] %}
|
||
<li><form method="post" action="{{ url_for('equipments.change_status', id=unit.id, status=value) }}"><button class="dropdown-item" type="submit">{{ label }}</button></form></li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endif %}
|
||
|
||
<!-- Entreprise extérieure -->
|
||
{% if intervention.company %}
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-truck text-orange"></i> Entreprise extérieure</h6>
|
||
<a href="{{ url_for('companies.detail', id=intervention.company.id) }}" class="text-decoration-none">
|
||
{{ intervention.company.name }}
|
||
</a>
|
||
{% if intervention.company.specialty %}
|
||
<span class="badge bg-secondary ms-1">{{ intervention.company.specialty }}</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Historique des changements de statut -->
|
||
{% if history %}
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-clock-history text-secondary"></i> Historique des statuts</h6>
|
||
<div class="timeline">
|
||
{% for change in history %}
|
||
<div class="d-flex align-items-start mb-2 pb-2 border-bottom">
|
||
<div class="me-3 text-center" style="min-width:50px;">
|
||
<span class="badge bg-{{ change.to_color }}">
|
||
<i class="bi bi-{{ statuses[change.to_status]['icon'] }}"></i>
|
||
</span>
|
||
</div>
|
||
<div class="flex-grow-1">
|
||
<strong>{{ change.to_label }}</strong>
|
||
<span class="text-muted small">(de {{ change.from_label }})</span>
|
||
<br>
|
||
<span class="text-muted small">
|
||
Par <strong>{{ change.changed_by.full_name if change.changed_by else 'Utilisateur supprimé' }}</strong> — {{ change.created_at|datetime_fmt if change.created_at else '—' }}
|
||
</span>
|
||
{% if change.comment %}
|
||
<br><em class="small">{{ change.comment }}</em>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Commentaires -->
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-chat-dots text-primary"></i> Commentaires</h6>
|
||
|
||
{% for comment in intervention.comments %}
|
||
<div class="d-flex mb-3 pb-2 border-bottom">
|
||
<div class="me-3">
|
||
<div class="avatar-placeholder bg-secondary text-white rounded-circle d-flex align-items-center justify-content-center" style="width:36px;height:36px;font-size:0.9rem;">
|
||
{{ comment.author.full_name[0]|upper }}
|
||
</div>
|
||
</div>
|
||
<div class="flex-grow-1">
|
||
<strong>{{ comment.author.full_name }}</strong>
|
||
<span class="text-muted small ms-2">{{ comment.created_at|datetime_fmt if comment.created_at else '—' }}</span>
|
||
<p class="mb-0 mt-1" style="white-space: pre-wrap;">{{ comment.content }}</p>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
<form method="POST" action="{{ url_for('interventions.add_comment', id=intervention.id) }}">
|
||
<div class="input-group">
|
||
<textarea name="content" class="form-control" placeholder="Ajouter un commentaire..." rows="2" required></textarea>
|
||
<button class="btn btn-outline-primary" type="submit"><i class="bi bi-send"></i></button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ═══ Colonne droite : infos synthétiques ═══ -->
|
||
<div class="col-lg-4">
|
||
|
||
<!-- Actions -->
|
||
<div class="d-grid gap-2 mb-3">
|
||
{% if intervention.is_deleted %}
|
||
<form method="POST" action="{{ url_for('interventions.restore', id=intervention.id) }}">
|
||
<button type="submit" class="btn btn-outline-success w-100" onclick="return confirm('Restaurer cette intervention ?')">
|
||
<i class="bi bi-arrow-counterclockwise"></i> Restaurer
|
||
</button>
|
||
</form>
|
||
{% else %}
|
||
<a href="{{ url_for('interventions.edit', id=intervention.id) }}" class="btn btn-outline-primary">
|
||
<i class="bi bi-pencil"></i> Modifier l'intervention
|
||
</a>
|
||
<form method="POST" action="{{ url_for('interventions.soft_delete', id=intervention.id) }}">
|
||
<button type="submit" class="btn btn-outline-danger w-100" onclick="return confirm('Mettre cette intervention en corbeille ?')">
|
||
<i class="bi bi-trash3"></i> Mettre en corbeille
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Infos clés -->
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-info-circle text-primary"></i> Résumé</h6>
|
||
<table class="table table-sm table-borderless mb-0">
|
||
<tr><td class="text-muted">Type</td><td class="fw-bold">
|
||
{% if intervention.type == 'curatif' %}🔧 Curatif{% else %}🛡️ Préventif{% endif %}
|
||
</td></tr>
|
||
<tr><td class="text-muted">Priorité</td><td><span class="badge bg-{{ intervention.priority_color }}">{{ intervention.priority_label }}</span></td></tr>
|
||
<tr><td class="text-muted">Statut</td><td><span class="badge bg-{{ intervention.status_color }}">{{ intervention.status_label }}</span></td></tr>
|
||
{% if intervention.recurrence_month %}
|
||
<tr><td class="text-muted">Récurrence</td><td>Tous les {{ intervention.recurrence_month }} mois</td></tr>
|
||
{% endif %}
|
||
{% if intervention.cost %}
|
||
<tr><td class="text-muted">Coût</td><td>{{ "%.2f"|format(intervention.cost) }} €</td></tr>
|
||
{% endif %}
|
||
{% if intervention.time_spent %}
|
||
<tr><td class="text-muted">Temps passé</td><td>{{ intervention.time_spent }} min</td></tr>
|
||
{% endif %}
|
||
<tr><td class="text-muted">Demandé le</td><td>{{ intervention.requested_at.strftime('%d/%m/%Y') if intervention.requested_at else '—' }}</td></tr>
|
||
{% if intervention.started_at %}
|
||
<tr><td class="text-muted">Débuté le</td><td>{{ intervention.started_at.strftime('%d/%m/%Y') }}</td></tr>
|
||
{% endif %}
|
||
{% if intervention.completed_at %}
|
||
<tr><td class="text-muted">Terminé le</td><td>{{ intervention.completed_at.strftime('%d/%m/%Y') }}</td></tr>
|
||
{% endif %}
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Demandeur -->
|
||
{% if intervention.requester_name %}
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-person text-secondary"></i> Demandeur</h6>
|
||
<strong>{{ intervention.requester_name }}</strong>
|
||
{% if intervention.requester_contact %}
|
||
<br><span class="text-muted small">{{ intervention.requester_contact }}</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Détails préventif -->
|
||
{% if intervention.type == 'preventif' and intervention.preventive_type %}
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-body">
|
||
<h6 class="card-title"><i class="bi bi-calendar-check text-success"></i> Détails préventif</h6>
|
||
<strong>Type :</strong> {{ intervention.preventive_type }}
|
||
{% if intervention.recurrence_month %}
|
||
<br><strong>Récurrence :</strong> Tous les {{ intervention.recurrence_month }} mois
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Documents -->
|
||
<div class="card border-0 shadow-sm mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center py-2">
|
||
<span><i class="bi bi-file-earmark"></i> Documents</span>
|
||
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#uploadInterventionDocumentModal">
|
||
<i class="bi bi-upload"></i>
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if intervention.documents.all() %}
|
||
<div class="list-group list-group-flush">
|
||
{% for doc in intervention.documents %}
|
||
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
|
||
<div>
|
||
{% set ext = doc.filename|lower %}
|
||
<i class="bi bi-file-earmark-{% if '.pdf' in ext %}pdf{% elif '.png' in ext or '.jpg' in ext or '.jpeg' in ext or '.gif' in ext %}image{% else %}text{% endif %}"></i>
|
||
{{ doc.filename }}
|
||
{% if doc.description %}
|
||
<br><small class="text-muted">{{ doc.description }}</small>
|
||
{% endif %}
|
||
<br><small class="text-muted">{{ doc.uploaded_at.strftime('%d/%m/%Y') if doc.uploaded_at else '—' }}</small>
|
||
</div>
|
||
<div>
|
||
<a href="{{ url_for('documents.download_intervention', doc_id=doc.id) }}" class="btn btn-sm btn-outline-secondary" title="Télécharger">
|
||
<i class="bi bi-download"></i>
|
||
</a>
|
||
<button type="button" class="btn btn-sm btn-outline-primary" title="Modifier la description" data-bs-toggle="modal" data-bs-target="#editInterventionDocumentModal{{ doc.id }}">
|
||
<i class="bi bi-pencil"></i>
|
||
</button>
|
||
<form method="POST" action="{{ url_for('documents.delete_intervention', doc_id=doc.id) }}" style="display:inline;" onsubmit="return confirm('Supprimer ce document ?')">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Supprimer">
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<p class="text-muted mb-0"><i class="bi bi-inbox"></i> Aucun document</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(() => {
|
||
const room = document.getElementById('intervention-room');
|
||
const category = document.getElementById('intervention-category');
|
||
const equipment = document.getElementById('intervention-equipment');
|
||
if (!room || !category || !equipment) return;
|
||
const filter = () => {
|
||
const roomId = room.value;
|
||
const categoryId = category.value;
|
||
[...equipment.options].forEach(option => {
|
||
if (!option.value) { option.hidden = false; return; }
|
||
const matchesRoom = !roomId || option.dataset.room === roomId;
|
||
const matchesCategory = !categoryId || option.dataset.category === categoryId;
|
||
option.hidden = !(matchesRoom && matchesCategory);
|
||
if (option.hidden && option.selected) equipment.value = '';
|
||
});
|
||
};
|
||
room.addEventListener('change', filter);
|
||
category.addEventListener('change', filter);
|
||
filter();
|
||
})();
|
||
</script>
|
||
|
||
<!-- Modal Upload Document Intervention -->
|
||
<div class="modal fade" id="uploadInterventionDocumentModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Ajouter un document</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<form method="POST" action="{{ url_for('documents.upload_intervention', intervention_id=intervention.id) }}" enctype="multipart/form-data">
|
||
<div class="modal-body">
|
||
<div class="mb-3">
|
||
<label class="form-label">Fichier</label>
|
||
<input type="file" name="file" class="form-control" required>
|
||
<div class="form-text">PDF, images, documents Office autorisés.</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Description</label>
|
||
<textarea name="description" class="form-control" rows="2" placeholder="Description optionnelle..."></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||
<button type="submit" class="btn btn-primary">Uploader</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modals Edit Documents Intervention -->
|
||
{% for doc in intervention.documents %}
|
||
<div class="modal fade" id="editInterventionDocumentModal{{ doc.id }}" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Modifier la description</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<form method="POST" action="{{ url_for('documents.edit_intervention', doc_id=doc.id) }}">
|
||
<div class="modal-body">
|
||
<p><strong>Fichier :</strong> {{ doc.filename }}</p>
|
||
<div class="mb-3">
|
||
<label class="form-label">Description</label>
|
||
<textarea name="description" class="form-control" rows="3">{{ doc.description or '' }}</textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
<!-- Styles custom pour le workflow -->
|
||
<style>
|
||
.bg-purple { background-color: #6f42c1 !important; }
|
||
.bg-orange { background-color: #fd7e14 !important; }
|
||
.avatar-placeholder { font-weight: 600; }
|
||
</style>
|
||
{% endblock %}
|