This commit is contained in:
Ross
2025-12-14 21:07:16 +00:00
parent 26d3ff7a2d
commit d6ede37e0b
2 changed files with 21 additions and 30 deletions
+6 -1
View File
@@ -47,13 +47,17 @@
document.body.addEventListener('htmx:afterSwap', function(evt){ document.body.addEventListener('htmx:afterSwap', function(evt){
try{ try{
const target = evt.detail && evt.detail.target; 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; if(!target || target.id !== 'modal') return;
const modal = document.getElementById('modal'); const modal = document.getElementById('modal');
if(!modal) return; if(!modal) return;
// If server returned empty content, hide modal and clear selections // If server returned empty content, hide modal and clear selections
const raw = modal.innerHTML || ''; 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()){ if(!raw.trim()){
try{ console.debug('[DEBUG] modal content empty — hiding modal'); }catch(e){}
modal.style.display='none'; 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){} try{ if(window.clearAllCalendarSelections) window.clearAllCalendarSelections(); else if(window._calendars) Object.values(window._calendars).forEach(c=>c.clearSelection && c.clearSelection()); }catch(e){}
return; return;
@@ -81,6 +85,7 @@
} }
if(contentElem){ if(contentElem){
try{ console.debug('[DEBUG] tagging existing element as modal-content:', contentElem); }catch(e){}
contentElem.classList.add('modal-content'); contentElem.classList.add('modal-content');
} else { } else {
// fallback: if there was no element to mark (unlikely), safely create a wrapper // fallback: if there was no element to mark (unlikely), safely create a wrapper
@@ -98,7 +103,7 @@
modal.appendChild(wrapper); modal.appendChild(wrapper);
}catch(e){ }catch(e){
// If moving nodes fails, log but don't throw — keep modal visible with whatever was returned. // 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);
} }
} }
} }
+15 -29
View File
@@ -266,15 +266,13 @@ def worker_create(request):
{"rota": rota}, {"rota": rota},
request=request, request=request,
) )
resp = ( resp = f'<div id="worker-list" hx-swap-oob="true">{worker_list_html}</div>'
f'<div id="worker-list" hx-swap-oob="true">{worker_list_html}</div>'
+ '<div id="modal" hx-swap-oob="true"></div>'
)
response = HttpResponse(resp) response = HttpResponse(resp)
# close modal via HX-Trigger to avoid racing OOB swaps that target #modal
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response
# If we couldn't render the worker list (no rota), just close modal # If we couldn't render the worker list (no rota), just close modal via trigger
response = HttpResponse('<div id="modal" hx-swap-oob="true"></div>') response = HttpResponse('')
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response
# Non-HTMX POST -> redirect # Non-HTMX POST -> redirect
@@ -332,10 +330,7 @@ def worker_edit(request, rota_id, worker_id):
form.save() form.save()
# Return updated worker list OOB and close modal # Return updated worker list OOB and close modal
worker_list_html = render_to_string("rota/partials/worker_list.html", {"rota": rota}, request=request) worker_list_html = render_to_string("rota/partials/worker_list.html", {"rota": rota}, request=request)
resp = ( resp = f'<div id="worker-list" hx-swap-oob="true">{worker_list_html}</div>'
f'<div id="worker-list" hx-swap-oob="true">{worker_list_html}</div>'
+ '<div id="modal" hx-swap-oob="true"></div>'
)
response = HttpResponse(resp) response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response
@@ -405,10 +400,7 @@ def worker_delete(request, rota_id, worker_id):
rota = get_object_or_404(RotaSchedule, pk=rota_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) worker_list_html = render_to_string("rota/partials/worker_list.html", {"rota": rota}, request=request)
resp = ( resp = f'<div id="worker-list" hx-swap-oob="true">{worker_list_html}</div>'
f'<div id="worker-list" hx-swap-oob="true">{worker_list_html}</div>'
+ '<div id="modal" hx-swap-oob="true"></div>'
)
response = HttpResponse(resp) response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response
@@ -515,10 +507,7 @@ def request_leave(request, token=None):
{"leaves": worker.leaves.all(), "token": token}, {"leaves": worker.leaves.all(), "token": token},
request=request, request=request,
) )
resp = ( resp = f'<div id="leave-list" hx-swap-oob="true">{leave_list_html}</div>'
f'<div id="leave-list" hx-swap-oob="true">{leave_list_html}</div>'
+ '<div id="modal" hx-swap-oob="true"></div>'
)
response = HttpResponse(resp) response = HttpResponse(resp)
# Trigger modal close and a leaveUpdated event so client can refresh calendar highlights # Trigger modal close and a leaveUpdated event so client can refresh calendar highlights
response["HX-Trigger"] = "closeModal,leaveUpdated" response["HX-Trigger"] = "closeModal,leaveUpdated"
@@ -633,7 +622,7 @@ def _oob_update_options_display(request, rota):
{"rota": rota, "options_pretty": options_pretty}, {"rota": rota, "options_pretty": options_pretty},
request=request, request=request,
) )
resp = '<div id="rota-options-display" hx-swap-oob="true">' + options_html + '</div>' + '<div id="modal" hx-swap-oob="true"></div>' resp = '<div id="rota-options-display" hx-swap-oob="true">' + options_html + '</div>'
response = HttpResponse(resp) response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response
@@ -943,11 +932,11 @@ def leaves_json(request, worker_id):
"""Return JSON list of leave ranges for a given worker.""" """Return JSON list of leave ranges for a given worker."""
worker = get_object_or_404(Worker, pk=worker_id) worker = get_object_or_404(Worker, pk=worker_id)
data = [] data = []
for l in worker.leaves.all().order_by('start_date'): for leave in worker.leaves.all().order_by('start_date'):
data.append({ data.append({
'start': l.start_date.isoformat(), 'start': leave.start_date.isoformat(),
'end': l.end_date.isoformat(), 'end': leave.end_date.isoformat(),
'reason': l.reason, 'reason': leave.reason,
}) })
return JsonResponse({'leaves': data}) return JsonResponse({'leaves': data})
@@ -1100,8 +1089,7 @@ def shift_add(request, rota_id):
resp = ( resp = (
f'<div id="shift-list" hx-swap-oob="true">{shift_list_html}</div>' f'<div id="shift-list" hx-swap-oob="true">{shift_list_html}</div>'
+ '<div id="shift-form" hx-swap-oob="true"></div>' + '<div id="shift-form" hx-swap-oob="true"></div>'
+ '<div id="modal" hx-swap-oob="true"></div>' )
)
response = HttpResponse(resp) response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response
@@ -1146,8 +1134,7 @@ def shift_edit(request, rota_id, idx):
resp = ( resp = (
f'<div id="shift-list" hx-swap-oob="true">{shift_list_html}</div>' f'<div id="shift-list" hx-swap-oob="true">{shift_list_html}</div>'
+ '<div id="shift-form" hx-swap-oob="true"></div>' + '<div id="shift-form" hx-swap-oob="true"></div>'
+ '<div id="modal" hx-swap-oob="true"></div>' )
)
response = HttpResponse(resp) response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response
@@ -1191,8 +1178,7 @@ def shift_delete(request, rota_id, idx):
resp = ( resp = (
f'<div id="shift-list" hx-swap-oob="true">{shift_list_html}</div>' f'<div id="shift-list" hx-swap-oob="true">{shift_list_html}</div>'
+ '<div id="shift-form" hx-swap-oob="true"></div>' + '<div id="shift-form" hx-swap-oob="true"></div>'
+ '<div id="modal" hx-swap-oob="true"></div>' )
)
response = HttpResponse(resp) response = HttpResponse(resp)
response["HX-Trigger"] = "closeModal" response["HX-Trigger"] = "closeModal"
return response return response