diff --git a/loko/interventions/models.py b/loko/interventions/models.py
index c1e91df..e5589b3 100644
--- a/loko/interventions/models.py
+++ b/loko/interventions/models.py
@@ -58,6 +58,10 @@ STATUS_ORDERS = {
'canceled': 200,
}
+# Numérotation 1-15 des statuts dans l'ordre canonique du stepper
+STATUS_STEP_NUMBERS = {key: idx + 1 for idx, key in enumerate(STATUS_CHOICES.keys())}
+
+
# Couleurs canoniques des statuts (hex). Source de vérité partagée avec status_colors.js.
STATUS_COLORS = {
'in_preparation': '#757575',
@@ -1328,6 +1332,32 @@ class InterventionTimeLine(models.Model):
def get_to_status_color(self):
return STATUS_COLOR_MAP.get(self.to_status, "secondary")
+ def get_from_status_number(self):
+ return STATUS_STEP_NUMBERS.get(self.from_status, '')
+
+ def get_to_status_number(self):
+ return STATUS_STEP_NUMBERS.get(self.to_status, '')
+
+ def get_from_status_hex(self):
+ return STATUS_COLORS.get(self.from_status, '#757575')
+
+ def get_to_status_hex(self):
+ return STATUS_COLORS.get(self.to_status, '#757575')
+
+ def get_from_status_text_color(self):
+ return '#000000' if self.from_status in STATUS_COLORS_DARK_TEXT else '#ffffff'
+
+ def get_to_status_text_color(self):
+ return '#000000' if self.to_status in STATUS_COLORS_DARK_TEXT else '#ffffff'
+
+ from_status_number = property(get_from_status_number)
+ to_status_number = property(get_to_status_number)
+ from_status_hex = property(get_from_status_hex)
+ to_status_hex = property(get_to_status_hex)
+ from_status_text_color = property(get_from_status_text_color)
+ to_status_text_color = property(get_to_status_text_color)
+
+
def get_badge_class(self):
return {
'creation': 'bg-success',
diff --git a/loko/interventions/static/interventions/timeline.css b/loko/interventions/static/interventions/timeline.css
index 2afcee2..040ec61 100644
--- a/loko/interventions/static/interventions/timeline.css
+++ b/loko/interventions/static/interventions/timeline.css
@@ -261,4 +261,66 @@
color: #ffffff !important;
}
+/* Transition de statut : boules de couleur avec numéro et libellé en-dessous */
+.history-status-transition {
+ display: inline-flex;
+ align-items: flex-start;
+}
+
+.history-status-step {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ width: 90px;
+ text-align: center;
+}
+
+.history-status-circle {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ margin-bottom: 3px;
+ flex-shrink: 0;
+ user-select: none;
+}
+
+.history-status-arrow {
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: #94a3b8;
+ font-size: 1.15rem;
+ padding: 0 6px;
+ flex-shrink: 0;
+}
+
+.history-status-label {
+ font-size: 11px;
+ font-weight: 500;
+ color: #334155;
+ line-height: 1.2;
+ text-align: center;
+ word-break: normal;
+ overflow-wrap: break-word;
+}
+
+.history-status-change-badge {
+ align-self: flex-start;
+ margin-top: 3px;
+}
+
+.history-status-desc {
+ margin-top: 5px;
+ font-style: italic;
+}
+
+
diff --git a/loko/interventions/templates/interventions/detail/card_chronologie.html b/loko/interventions/templates/interventions/detail/card_chronologie.html
index 9699847..0771ec2 100644
--- a/loko/interventions/templates/interventions/detail/card_chronologie.html
+++ b/loko/interventions/templates/interventions/detail/card_chronologie.html
@@ -55,14 +55,28 @@
{% if event.event_type == 'status_change' %}
-
{% translate "Statut" %}
- {% if event.from_status %}
-
{{ event.get_from_status_display }}
-
- {% endif %}
-
{{ event.get_to_status_display }}
+
{% translate "Statut" %}
+
+ {% if event.from_status %}
+
+
+ {{ event.from_status_number }}
+
+
{{ event.get_from_status_display }}
+
+
+
+
+ {% endif %}
+
+
+ {{ event.to_status_number }}
+
+
{{ event.get_to_status_display }}
+
+
{% if event.event_description %}
-
({{ event.event_description }})
+
({{ event.event_description }})
{% endif %}
{% elif event.event_type == 'assignment' %}
{% translate "Assignation" %}
diff --git a/loko/interventions/templates/interventions/detail/styles.html b/loko/interventions/templates/interventions/detail/styles.html
index 4b26e8d..1c79fe1 100644
--- a/loko/interventions/templates/interventions/detail/styles.html
+++ b/loko/interventions/templates/interventions/detail/styles.html
@@ -606,4 +606,65 @@
background-color: #6f42c1 !important;
color: #ffffff !important;
}
+
+ /* Transition de statut : boules de couleur avec numéro et libellé en-dessous */
+ .history-status-transition {
+ display: inline-flex;
+ align-items: flex-start;
+ }
+
+ .history-status-step {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ width: 90px;
+ text-align: center;
+ }
+
+ .history-status-circle {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ margin-bottom: 3px;
+ flex-shrink: 0;
+ user-select: none;
+ }
+
+ .history-status-arrow {
+ height: 28px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: #94a3b8;
+ font-size: 1.15rem;
+ padding: 0 6px;
+ flex-shrink: 0;
+ }
+
+ .history-status-label {
+ font-size: 11px;
+ font-weight: 500;
+ color: #334155;
+ line-height: 1.2;
+ text-align: center;
+ word-break: normal;
+ overflow-wrap: break-word;
+ }
+
+ .history-status-change-badge {
+ align-self: flex-start;
+ margin-top: 3px;
+ }
+
+ .history-status-desc {
+ margin-top: 5px;
+ font-style: italic;
+ }
diff --git a/loko/interventions/tests/test_timeline_history.py b/loko/interventions/tests/test_timeline_history.py
index bb34cc4..27e0c93 100644
--- a/loko/interventions/tests/test_timeline_history.py
+++ b/loko/interventions/tests/test_timeline_history.py
@@ -81,7 +81,7 @@ class TimelineHistoryTests(TestCase):
event_time=timezone.now(),
event_type='status_change',
from_status='in_preparation',
- to_status='planned',
+ to_status='to_be_planned',
event_description='Passage en planifié'
)
self.assertTrue(event_status.is_status_change)
@@ -90,6 +90,12 @@ class TimelineHistoryTests(TestCase):
self.assertFalse(event_status.is_assignment)
self.assertEqual(event_status.get_badge_class(), 'bg-purple')
self.assertEqual(event_status.get_icon(), 'bi-arrow-repeat')
+ self.assertEqual(event_status.from_status_number, 1)
+ self.assertEqual(event_status.to_status_number, 3)
+ self.assertEqual(event_status.from_status_hex, '#757575')
+ self.assertEqual(event_status.to_status_hex, '#ff8a80')
+ self.assertEqual(event_status.from_status_text_color, '#ffffff')
+ self.assertEqual(event_status.to_status_text_color, '#000000')
# Assignment event
event_assign = InterventionTimeLine.objects.create(
@@ -450,6 +456,7 @@ class TimelineHistoryTests(TestCase):
def test_detail_view_shows_all_in_full_history(self):
"""Test that notes, documents, and links appear as detailed events in timeline."""
+ record_timeline_event(self.intervention, self.user, event_type='status_change', from_status='in_preparation', to_status='to_be_processed')
record_note_event(self.intervention, self.user, action='create', note_type='comment', content='Note test')
record_document_event(self.intervention, self.user, action='upload', filenames=['doc_test.png'])
record_relation_link_event(self.intervention, self.user, relation_label='Intervention', target_labels=['I999999'])
@@ -467,7 +474,8 @@ class TimelineHistoryTests(TestCase):
self.assertContains(response, 'data-event-type="document"')
self.assertContains(response, 'data-event-type="link"')
self.assertContains(response, 'history-event-badge')
- self.assertContains(response, 'history-status-badge')
+ self.assertContains(response, 'history-status-circle')
+ self.assertContains(response, 'history-status-transition')
self.assertContains(response, 'bg-purple')