feat: durées en minutes + feedback visuel du retraitement
- Filtre format_duration : affiche les temps en min/s au lieu de secondes brutes - Bouton reprocess : spinner animé, compteur temps réel, confirmation immédiate Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -161,6 +161,19 @@ _SEVERITY_STYLES = {
|
||||
}
|
||||
|
||||
|
||||
def format_duration(seconds: float | None) -> str:
|
||||
"""Formate une durée en secondes vers un format lisible (ex: 2min 30s)."""
|
||||
if seconds is None:
|
||||
return ""
|
||||
if seconds < 60:
|
||||
return f"{seconds:.1f}s"
|
||||
minutes = int(seconds // 60)
|
||||
secs = int(seconds % 60)
|
||||
if secs == 0:
|
||||
return f"{minutes}min"
|
||||
return f"{minutes}min {secs:02d}s"
|
||||
|
||||
|
||||
def severity_badge(value: str | None) -> Markup:
|
||||
if not value or value not in _SEVERITY_STYLES:
|
||||
return Markup("")
|
||||
@@ -182,6 +195,7 @@ def create_app() -> Flask:
|
||||
app.jinja_env.filters["confidence_badge"] = confidence_badge
|
||||
app.jinja_env.filters["confidence_label"] = confidence_label
|
||||
app.jinja_env.filters["severity_badge"] = severity_badge
|
||||
app.jinja_env.filters["format_duration"] = format_duration
|
||||
|
||||
ccam_dict = load_ccam_dict()
|
||||
|
||||
|
||||
@@ -198,6 +198,18 @@
|
||||
/* Source files */
|
||||
.source-files { font-size: 0.8rem; color: #64748b; margin-top: 0.5rem; }
|
||||
.source-files code { background: #f1f5f9; padding: 1px 4px; border-radius: 3px; }
|
||||
|
||||
/* Spinner animation */
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{% endif %}
|
||||
<div class="group-title" style="margin-top:1.5rem;">Actions</div>
|
||||
<button id="reprocess-btn" style="width:100%;padding:0.6rem;background:#3b82f6;color:white;border:none;border-radius:0.375rem;cursor:pointer;font-size:0.875rem;font-weight:600;margin-bottom:0.5rem;">Relancer l'étude</button>
|
||||
<div id="reprocess-status" style="font-size:0.75rem;padding:0.25rem;"></div>
|
||||
<div id="reprocess-status" style="font-size:0.75rem;padding:0.25rem;min-height:1.5rem;"></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -33,7 +33,7 @@
|
||||
{% if dossier.processing_time_s is not none %}
|
||||
<div class="info-item">
|
||||
<label>Temps de traitement</label>
|
||||
<span>{{ dossier.processing_time_s }}s</span>
|
||||
<span>{{ dossier.processing_time_s|format_duration }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -286,29 +286,41 @@ document.getElementById('reprocess-btn').addEventListener('click', async () => {
|
||||
const status = document.getElementById('reprocess-status');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Traitement en cours...';
|
||||
status.textContent = '';
|
||||
status.style.color = '#3b82f6';
|
||||
btn.style.background = '#64748b';
|
||||
btn.innerHTML = '<span style="display:inline-flex;align-items:center;gap:0.4rem;"><span class="spinner"></span> Traitement en cours...</span>';
|
||||
status.innerHTML = '<span style="color:#3b82f6;">Demande envoyée, traitement lancé. Veuillez patienter...</span>';
|
||||
|
||||
const startTime = Date.now();
|
||||
const timer = setInterval(() => {
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
const min = Math.floor(elapsed / 60);
|
||||
const sec = elapsed % 60;
|
||||
const timeStr = min > 0 ? min + 'min ' + String(sec).padStart(2, '0') + 's' : sec + 's';
|
||||
status.innerHTML = '<span style="color:#3b82f6;">Traitement en cours... ' + timeStr + '</span>';
|
||||
}, 1000);
|
||||
|
||||
try {
|
||||
const response = await fetch('/reprocess/{{ filepath }}', { method: 'POST' });
|
||||
clearInterval(timer);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.ok) {
|
||||
status.textContent = data.message;
|
||||
status.style.color = '#16a34a';
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
status.innerHTML = '<span style="color:#16a34a;font-weight:600;">Traitement terminé. Rechargement...</span>';
|
||||
btn.style.background = '#16a34a';
|
||||
btn.innerHTML = 'Terminé';
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
} else {
|
||||
status.textContent = (data.error || 'Erreur');
|
||||
status.style.color = '#dc2626';
|
||||
status.innerHTML = '<span style="color:#dc2626;">' + (data.error || 'Erreur') + '</span>';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Relancer l\'étude';
|
||||
btn.style.background = '#3b82f6';
|
||||
btn.innerHTML = 'Relancer l\'étude';
|
||||
}
|
||||
} catch (err) {
|
||||
status.textContent = 'Erreur réseau';
|
||||
status.style.color = '#dc2626';
|
||||
clearInterval(timer);
|
||||
status.innerHTML = '<span style="color:#dc2626;">Erreur réseau</span>';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Relancer l\'étude';
|
||||
btn.style.background = '#3b82f6';
|
||||
btn.innerHTML = 'Relancer l\'étude';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<h3 style="display:flex;align-items:baseline;gap:0.75rem;flex-wrap:wrap;">
|
||||
{{ group_name }}
|
||||
<span style="font-size:0.75rem;font-weight:400;color:#64748b;">
|
||||
{{ items|length }} fichier(s){% if ns.count %} — total : {{ ns.total|round(1) }}s{% endif %}
|
||||
{{ items|length }} fichier(s){% if ns.count %} — total : {{ ns.total|format_duration }}{% endif %}
|
||||
</span>
|
||||
{% if stats %}
|
||||
<span class="badge-count badge-das">{{ stats.das_count }} DAS</span>
|
||||
@@ -84,7 +84,7 @@
|
||||
{% endif %}
|
||||
{% if item.dossier.processing_time_s is not none %}
|
||||
<div style="margin-top:0.5rem;font-size:0.75rem;color:#64748b;">
|
||||
Traitement : {{ item.dossier.processing_time_s }}s
|
||||
Traitement : {{ item.dossier.processing_time_s|format_duration }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user