From d6ede37e0b0cb86d11131fa6913efa698b48376e Mon Sep 17 00:00:00 2001 From: Ross Date: Sun, 14 Dec 2025 21:07:16 +0000 Subject: [PATCH] . --- djangorota/djangorota/templates/base.html | 7 +++- djangorota/rota/views.py | 44 ++++++++--------------- 2 files changed, 21 insertions(+), 30 deletions(-) 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