326 lines
No EOL
17 KiB
HTML
326 lines
No EOL
17 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ message.subject or '(sans sujet)' }} - ENT77{% endblock %}
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<div class="mb-3">
|
|
<a href="{{ url_for('ent.inbox') }}" class="btn btn-outline-secondary btn-sm">
|
|
<i class="bi bi-arrow-left"></i> Retour à la boîte
|
|
</a>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Message à gauche -->
|
|
<div class="col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h4 class="mb-1">{{ message.subject or '(sans sujet)' }}</h4>
|
|
<div class="text-muted">
|
|
<strong>De:</strong> {{ message.sender_name or 'Inconnu' }}
|
|
{% if message.sender_email %}
|
|
<{{ message.sender_email }}>
|
|
{% endif %}
|
|
</div>
|
|
{% if message.recipients %}
|
|
<div class="text-muted small">
|
|
<strong>À:</strong> {{ message.recipients }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="text-end">
|
|
<div class="text-muted small">
|
|
{{ message.date.strftime('%d/%m/%Y %H:%M') if message.date else '-' }}
|
|
</div>
|
|
{% if message.has_attachment %}
|
|
<span class="badge bg-info"><i class="bi bi-paperclip"></i> Pièce jointe</span>
|
|
{% endif %}
|
|
{% if message.is_forwarded %}
|
|
<span class="badge bg-secondary">Transféré</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="email-content">
|
|
{{ message.body|safe if message.body else '<p class="text-muted">(pas de contenu)</p>' }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pièces jointes -->
|
|
{% if attachments %}
|
|
<div class="card-footer">
|
|
<h6><i class="bi bi-paperclip"></i> Pièces jointes</h6>
|
|
<div>
|
|
{% for att in attachments %}
|
|
<div class="d-inline-block me-2 mb-2">
|
|
<a href="{{ url_for('ent.download_attachment', att_id=att.id) }}"
|
|
class="btn btn-outline-secondary btn-sm">
|
|
<i class="bi bi-download"></i> {{ att.name }}
|
|
<small class="text-muted">({{ (att.size / 1024)|round(1) }} KB)</small>
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Panneau Hermes à droite -->
|
|
<div class="col-lg-4">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span><i class="bi bi-robot"></i> Interprétation GMAO</span>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{{ url_for('outlook.context_config') }}" class="btn btn-outline-secondary" title="Configurer le contexte GMAO">
|
|
<i class="bi bi-gear"></i>
|
|
</a>
|
|
<button class="btn btn-outline-primary" onclick="showInterpretForm()">
|
|
<i class="bi bi-plus"></i> Nouvelle
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Alerte contexte GMAO -->
|
|
<div class="alert alert-info small mb-3" id="context-alert">
|
|
<i class="bi bi-info-circle"></i>
|
|
<span id="context-status">Chargement du contexte...</span>
|
|
<a href="{{ url_for('outlook.context_config') }}" class="alert-link">Modifier</a>
|
|
</div>
|
|
|
|
<!-- Formulaire nouvelle interprétation -->
|
|
<div id="interpret-form" style="display: none;">
|
|
<div class="mb-3">
|
|
<label class="form-label">Contexte additionnel (optionnel)</label>
|
|
<textarea id="user-context" class="form-control" rows="3"
|
|
placeholder="Ajoutez un contexte spécifique pour ce message..."></textarea>
|
|
<div class="form-text">Le contexte GMAO global sera automatiquement inclus.</div>
|
|
</div>
|
|
<button id="interpret-btn" class="btn btn-primary w-100" onclick="interpretMessage()">
|
|
<span class="spinner-border spinner-border-sm d-none" id="interpret-spinner"></span>
|
|
<i class="bi bi-magic"></i> Interpréter avec Hermes
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Liste des interprétations -->
|
|
<div id="interpretations-list">
|
|
{% if interpretations %}
|
|
{% for interp in interpretations %}
|
|
<div class="interpretation-card mb-3 p-3 border rounded" id="interp-{{ interp.id }}">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<small class="text-muted">{{ interp.created_at.strftime('%d/%m/%Y %H:%M') if interp.created_at else '-' }}</small>
|
|
<span class="badge
|
|
{% if interp.status == 'pending' %}bg-warning text-dark
|
|
{% elif interp.status == 'accepted' %}bg-success
|
|
{% else %}bg-secondary{% endif %}">
|
|
{{ 'En attente' if interp.status == 'pending' else ('Acceptée' if interp.status == 'accepted' else 'Rejetée') }}
|
|
</span>
|
|
</div>
|
|
|
|
{% if interp.analysis_type %}
|
|
<!-- Analyse structurée -->
|
|
<div class="analysis-structured mb-2">
|
|
{% if interp.analysis_type == 'curative' %}
|
|
<span class="badge bg-danger mb-2"><i class="bi bi-exclamation-triangle"></i> Curative</span>
|
|
{% elif interp.analysis_type == 'preventive' %}
|
|
<span class="badge bg-primary mb-2"><i class="bi bi-calendar-check"></i> Préventive</span>
|
|
{% elif interp.analysis_type == 'administrative' %}
|
|
<span class="badge bg-warning text-dark mb-2"><i class="bi bi-clipboard-check"></i> Administrative</span>
|
|
{% elif interp.analysis_type == 'formation' %}
|
|
<span class="badge bg-info text-dark mb-2"><i class="bi bi-mortarboard"></i> Formation</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary mb-2"><i class="bi bi-info-circle"></i> Information</span>
|
|
{% endif %}
|
|
|
|
{% if interp.analysis_action == 'create' %}
|
|
<span class="badge bg-success mb-2"><i class="bi bi-plus-circle"></i> Nouvelle intervention</span>
|
|
{% elif interp.analysis_action == 'update' %}
|
|
<span class="badge bg-warning text-dark mb-2"><i class="bi bi-pencil"></i> Mise à jour</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary mb-2"><i class="bi bi-chat"></i> Information</span>
|
|
{% endif %}
|
|
|
|
{% if interp.analysis_priority %}
|
|
<span class="badge
|
|
{% if interp.analysis_priority == 'critical' %}bg-danger
|
|
{% elif interp.analysis_priority == 'high' %}bg-warning text-dark
|
|
{% elif interp.analysis_priority == 'medium' %}bg-info text-dark
|
|
{% else %}bg-secondary{% endif %} mb-2">
|
|
{{ 'Critique' if interp.analysis_priority == 'critical' else ('Haute' if interp.analysis_priority == 'high' else ('Normale' if interp.analysis_priority == 'medium' else 'Basse')) }}
|
|
</span>
|
|
{% endif %}
|
|
|
|
{% if interp.analysis_summary %}
|
|
<div class="mt-2">
|
|
<strong><i class="bi bi-chat-left-text"></i> {{ interp.analysis_summary[:100] }}{% if interp.analysis_summary|length > 100 %}...{% endif %}</strong>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row mt-2 small">
|
|
{% if interp.suggested_equipment %}
|
|
<div class="col-12">
|
|
<i class="bi bi-tools"></i> <strong>Équipement:</strong> {{ interp.suggested_equipment }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if interp.suggested_description %}
|
|
<div class="mt-2 small text-muted">
|
|
<strong>Description:</strong> {{ interp.suggested_description[:200] }}{% if interp.suggested_description|length > 200 %}...{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Boutons d'action -->
|
|
{% if interp.analysis_action == 'create' and interp.status == 'pending' %}
|
|
<div class="mt-3 d-flex gap-2">
|
|
<a href="{{ url_for('interventions.create', from_interpretation=interp.id, source='ent') }}"
|
|
class="btn btn-success btn-sm">
|
|
<i class="bi bi-plus-circle"></i> Créer l'intervention
|
|
</a>
|
|
<button class="btn btn-outline-danger btn-sm" onclick="rejectInterpretation({{ interp.id }})">
|
|
<i class="bi bi-x-circle"></i> Rejeter
|
|
</button>
|
|
</div>
|
|
{% elif interp.status == 'accepted' %}
|
|
<div class="mt-3">
|
|
<span class="text-success small"><i class="bi bi-check-circle"></i> Intervention créée</span>
|
|
</div>
|
|
{% elif interp.status == 'rejected' %}
|
|
<div class="mt-3">
|
|
<span class="text-muted small"><i class="bi bi-x-circle"></i> Interprétation rejetée</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<!-- Ancienne interprétation non structurée -->
|
|
<div class="interpretation-text">
|
|
{{ interp.interpretation|replace('\n', '<br>')|safe if interp.interpretation else 'Pas d\'interprétation' }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="bi bi-lightbulb display-4"></i>
|
|
<p class="mt-2">Aucune interprétation</p>
|
|
<button class="btn btn-primary" onclick="showInterpretForm()">
|
|
<i class="bi bi-magic"></i> Interpréter ce message
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.email-content {
|
|
max-height: 60vh;
|
|
overflow-y: auto;
|
|
}
|
|
.email-content img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
.email-content table {
|
|
max-width: 100%;
|
|
overflow-x: auto;
|
|
display: block;
|
|
}
|
|
.email-content a {
|
|
word-break: break-all;
|
|
}
|
|
.interpretation-card {
|
|
background: #f8f9fa;
|
|
}
|
|
.interpretation-text {
|
|
font-size: 0.9rem;
|
|
line-height: 1.5;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
function showInterpretForm() {
|
|
document.getElementById('interpret-form').style.display = 'block';
|
|
document.getElementById('user-context').focus();
|
|
}
|
|
|
|
function interpretMessage() {
|
|
const btn = document.getElementById('interpret-btn');
|
|
const spinner = document.getElementById('interpret-spinner');
|
|
const context = document.getElementById('user-context').value;
|
|
|
|
btn.disabled = true;
|
|
spinner.classList.remove('d-none');
|
|
|
|
fetch('/ent/api/interpret/{{ message.id }}', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({context: context})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Erreur: ' + (data.error || 'Erreur inconnue'));
|
|
btn.disabled = false;
|
|
spinner.classList.add('d-none');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Erreur: ' + error.message);
|
|
btn.disabled = false;
|
|
spinner.classList.add('d-none');
|
|
});
|
|
}
|
|
|
|
function rejectInterpretation(id) {
|
|
if (!confirm('Rejeter cette interprétation ?')) return;
|
|
|
|
fetch('/ent/api/interpretation/' + id + '/reject', {method: 'POST'})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Erreur: ' + (data.error || 'Erreur inconnue'));
|
|
}
|
|
});
|
|
}
|
|
|
|
// Charger le statut du contexte GMAO au chargement de la page
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadContextStatus();
|
|
});
|
|
|
|
function loadContextStatus() {
|
|
fetch('/outlook/api/context')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const statusEl = document.getElementById('context-status');
|
|
if (data.full_context && data.full_context.trim()) {
|
|
const sections = [
|
|
data.establishment_name,
|
|
data.technical_context,
|
|
data.equipment_context,
|
|
data.procedure_context,
|
|
data.staff_info,
|
|
data.contact_info,
|
|
data.hermes_instructions
|
|
].filter(s => s && s.trim()).length;
|
|
|
|
statusEl.innerHTML = `<strong>Contexte GMAO configuré</strong> (${sections} section${sections > 1 ? 's' : ''} remplie${sections > 1 ? 's' : ''})`;
|
|
} else {
|
|
statusEl.innerHTML = `<strong class="text-warning">Aucun contexte GMAO configuré</strong> - `;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('context-status').innerHTML = '<strong class="text-danger">Erreur de chargement</strong>';
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |