diff --git a/app/templates/semaine.html b/app/templates/semaine.html
index bec5ba3..3e91707 100644
--- a/app/templates/semaine.html
+++ b/app/templates/semaine.html
@@ -375,10 +375,14 @@ document.addEventListener('input', function(e) {
var timer = null;
var pendingDate = null;
- function triggerSave(date) {
+ // Déclenche la sauvegarde du jour depuis le témoin .autosave (attributs hx-*),
+ // exposé globalement : utilisé par le debounce ci-dessous et par la
+ // suppression d'une ligne d'intervention, qui doit être enregistrée
+ // immédiatement — sinon elle réapparaît au rechargement.
+ window.asSaveDay = function (date) {
var trig = document.getElementById('save-' + date);
if (trig) htmx.trigger(trig, 'save-day');
- }
+ };
// Le swap OOB remplace les champs du jour : si le focus était dedans
// (ex. tabulation entrée → sortie), on le restaure sur le champ équivalent.
@@ -395,7 +399,7 @@ document.addEventListener('input', function(e) {
}
pendingDate = date;
clearTimeout(timer);
- timer = setTimeout(function () { triggerSave(pendingDate); }, 350);
+ timer = setTimeout(function () { asSaveDay(pendingDate); }, 350);
});
document.body.addEventListener('htmx:afterSwap', function () {
@@ -502,6 +506,18 @@ document.addEventListener('htmx:afterSwap', asRestore);
// settle, dernier moment du cycle.
document.addEventListener('htmx:afterSettle', asRestore);
+// Supprime une ligne d'intervention et enregistre le jour immédiatement :
+// sans sauvegarde, la ligne ne disparaîtrait que visuellement et reviendrait
+// au rechargement (l'autosave ne se déclenche que sur change d'un champ).
+window.asDelEvent = function (btn) {
+ var ev = btn.closest('.as-event');
+ if (!ev) return;
+ var input = ev.querySelector('[data-date]');
+ var date = input ? input.dataset.date : null;
+ ev.remove();
+ if (date) asSaveDay(date);
+};
+
// Ajoute une ligne d'intervention vierge dans le jour ciblé
window.asAddEvent = function (date) {
var grid = document.getElementById('as-events-' + date);
@@ -513,7 +529,7 @@ window.asAddEvent = function (date) {
'' +
'
' +
'' +
- '' +
+ '' +
'
';
grid.appendChild(ev);
};
diff --git a/app/tests/test_autosave_astreinte.py b/app/tests/test_autosave_astreinte.py
index f1c79d8..da3b3cf 100644
--- a/app/tests/test_autosave_astreinte.py
+++ b/app/tests/test_autosave_astreinte.py
@@ -81,3 +81,48 @@ def test_ligne_entierement_vide_non_conservee():
"astreinte_desc": ["", ""]}, follow_redirects=False)
# La ligne vide n'est pas stockée, la ligne complète l'est
assert _events(uid, "2026-07-14") == [{"debut": "22:00", "fin": "23:00", "desc": None}]
+
+
+def test_suppression_dune_ligne_persiste():
+ """Le bouton × retire la ligne du DOM puis enregistre le jour : le POST ne
+ contient plus que les lignes restantes, la supprimée doit disparaître."""
+ uid = "u-as-del"
+ cookies = _login(uid)
+ models.toggle_astreinte("2026-07-14", uid)
+
+ client.post("/pointage/2026-07-14", cookies=cookies,
+ data={"astreinte_debut": ["22:00", "23:00"], "astreinte_fin": ["22:30", "23:30"],
+ "astreinte_desc": ["", ""]}, follow_redirects=False)
+ assert len(_events(uid, "2026-07-14") or []) == 2
+
+ # La 1re ligne est supprimée : seule la 2e reste dans le POST
+ r = client.post("/pointage/2026-07-14", cookies=cookies,
+ data={"astreinte_debut": ["23:00"], "astreinte_fin": ["23:30"],
+ "astreinte_desc": [""]}, follow_redirects=False)
+ assert r.status_code == 200, r.text
+ assert _events(uid, "2026-07-14") == [{"debut": "23:00", "fin": "23:30", "desc": None}]
+
+ # Suppression de la dernière ligne : plus aucun champ astreinte dans le POST
+ r = client.post("/pointage/2026-07-14", cookies=cookies,
+ data={"astreinte_debut": [], "astreinte_fin": [],
+ "astreinte_desc": []}, follow_redirects=False)
+ assert r.status_code == 200, r.text
+ assert _events(uid, "2026-07-14") == []
+
+
+def test_bouton_suppression_câble_lenregistrement():
+ """Le × doit déclencher la sauvegarde du jour (asDelEvent), sans quoi la
+ ligne ne disparaît que visuellement et revient au rechargement."""
+ uid = "u-as-del-html"
+ cookies = _login(uid)
+ models.toggle_astreinte("2026-07-14", uid)
+ client.post("/pointage/2026-07-14", cookies=cookies,
+ data={"astreinte_debut": ["22:00"], "astreinte_fin": ["22:30"],
+ "astreinte_desc": [""]}, follow_redirects=False)
+
+ r = client.get("/semaine/2026/29", cookies=cookies, follow_redirects=False)
+ assert r.status_code == 200, r.text
+ # Le bouton rendu appelle asDelEvent, défini (avec asSaveDay) dans la page
+ assert 'onclick="asDelEvent(this)"' in r.text
+ assert "window.asDelEvent" in r.text
+ assert "window.asSaveDay" in r.text