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