diff --git a/djangorota/djangorota/templates/base.html b/djangorota/djangorota/templates/base.html
index e13e0eb..2e32b34 100644
--- a/djangorota/djangorota/templates/base.html
+++ b/djangorota/djangorota/templates/base.html
@@ -47,13 +47,17 @@
document.body.addEventListener('htmx:afterSwap', function(evt){
try{
const target = evt.detail && evt.detail.target;
+ // DEBUG: log HTMX swap target and modal children to help diagnose intermittent swapErrors
+ try{ console.debug('[DEBUG] htmx:afterSwap target=', target && (target.id || target), ' evt.detail=', evt.detail); }catch(e){}
if(!target || target.id !== 'modal') return;
const modal = document.getElementById('modal');
if(!modal) return;
// If server returned empty content, hide modal and clear selections
const raw = modal.innerHTML || '';
+ try{ console.debug('[DEBUG] modal.innerHTML length=', raw.length, ' modal.children=', Array.from(modal.children).map(c => c.nodeName + (c.id ? '#'+c.id : '') + (c.className ? '.'+c.className.split(' ').join('.') : ''))); }catch(e){}
if(!raw.trim()){
+ try{ console.debug('[DEBUG] modal content empty — hiding modal'); }catch(e){}
modal.style.display='none';
try{ if(window.clearAllCalendarSelections) window.clearAllCalendarSelections(); else if(window._calendars) Object.values(window._calendars).forEach(c=>c.clearSelection && c.clearSelection()); }catch(e){}
return;
@@ -81,6 +85,7 @@
}
if(contentElem){
+ try{ console.debug('[DEBUG] tagging existing element as modal-content:', contentElem); }catch(e){}
contentElem.classList.add('modal-content');
} else {
// fallback: if there was no element to mark (unlikely), safely create a wrapper
@@ -98,7 +103,7 @@
modal.appendChild(wrapper);
}catch(e){
// If moving nodes fails, log but don't throw — keep modal visible with whatever was returned.
- console.error('modal wrapping fallback failed', e);
+ console.error('[DEBUG] modal wrapping fallback failed', e);
}
}
}
diff --git a/djangorota/rota/views.py b/djangorota/rota/views.py
index 2e23373..437b250 100644
--- a/djangorota/rota/views.py
+++ b/djangorota/rota/views.py
@@ -266,15 +266,13 @@ def worker_create(request):
{"rota": rota},
request=request,
)
- resp = (
- f'
{worker_list_html}
'
- + ''
- )
+ resp = f'{worker_list_html}
'
response = HttpResponse(resp)
+ # close modal via HX-Trigger to avoid racing OOB swaps that target #modal
response["HX-Trigger"] = "closeModal"
return response
- # If we couldn't render the worker list (no rota), just close modal
- response = HttpResponse('')
+ # If we couldn't render the worker list (no rota), just close modal via trigger
+ response = HttpResponse('')
response["HX-Trigger"] = "closeModal"
return response
# Non-HTMX POST -> redirect
@@ -332,10 +330,7 @@ def worker_edit(request, rota_id, worker_id):
form.save()
# Return updated worker list OOB and close modal
worker_list_html = render_to_string("rota/partials/worker_list.html", {"rota": rota}, request=request)
- resp = (
- f'{worker_list_html}
'
- + ''
- )
+ resp = f'{worker_list_html}
'
response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal"
return response
@@ -405,10 +400,7 @@ def worker_delete(request, rota_id, worker_id):
rota = get_object_or_404(RotaSchedule, pk=rota_id)
worker_list_html = render_to_string("rota/partials/worker_list.html", {"rota": rota}, request=request)
- resp = (
- f'{worker_list_html}
'
- + ''
- )
+ resp = f'{worker_list_html}
'
response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal"
return response
@@ -515,10 +507,7 @@ def request_leave(request, token=None):
{"leaves": worker.leaves.all(), "token": token},
request=request,
)
- resp = (
- f'{leave_list_html}
'
- + ''
- )
+ resp = f'{leave_list_html}
'
response = HttpResponse(resp)
# Trigger modal close and a leaveUpdated event so client can refresh calendar highlights
response["HX-Trigger"] = "closeModal,leaveUpdated"
@@ -633,7 +622,7 @@ def _oob_update_options_display(request, rota):
{"rota": rota, "options_pretty": options_pretty},
request=request,
)
- resp = '' + options_html + '
' + ''
+ resp = '' + options_html + '
'
response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal"
return response
@@ -943,11 +932,11 @@ def leaves_json(request, worker_id):
"""Return JSON list of leave ranges for a given worker."""
worker = get_object_or_404(Worker, pk=worker_id)
data = []
- for l in worker.leaves.all().order_by('start_date'):
+ for leave in worker.leaves.all().order_by('start_date'):
data.append({
- 'start': l.start_date.isoformat(),
- 'end': l.end_date.isoformat(),
- 'reason': l.reason,
+ 'start': leave.start_date.isoformat(),
+ 'end': leave.end_date.isoformat(),
+ 'reason': leave.reason,
})
return JsonResponse({'leaves': data})
@@ -1100,8 +1089,7 @@ def shift_add(request, rota_id):
resp = (
f'{shift_list_html}
'
+ ''
- + ''
- )
+ )
response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal"
return response
@@ -1146,8 +1134,7 @@ def shift_edit(request, rota_id, idx):
resp = (
f'{shift_list_html}
'
+ ''
- + ''
- )
+ )
response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal"
return response
@@ -1191,8 +1178,7 @@ def shift_delete(request, rota_id, idx):
resp = (
f'{shift_list_html}
'
+ ''
- + ''
- )
+ )
response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal"
return response