diff --git a/atlas/templates/atlas/collection_case_view_take.html b/atlas/templates/atlas/collection_case_view_take.html index 53fc0023..5cf98fed 100644 --- a/atlas/templates/atlas/collection_case_view_take.html +++ b/atlas/templates/atlas/collection_case_view_take.html @@ -25,6 +25,7 @@
+ {% endif %} @@ -414,15 +415,35 @@ var $next = $form.find('button[name="next"]'); var $finish = $form.find('button[name="finish"]'); - if ($next.length) { - $next.prop('disabled', false); - $next.click(); - } else if ($finish.length) { - $finish.prop('disabled', false); - $finish.click(); - } else { - $form.submit(); - } + // Instead of navigating away, submit via fetch with timed_out flag + // Use HTMX to POST the timed_out flag and then lock the UI + var onAfter = function (evt) { + try { + // lock the form UI in-place + $form.find('input, textarea, select, button').prop('disabled', true); + $form.addClass('timed-out-locked'); + $('#question-timer').text('Locked'); + + // hide the progress bar + $('#question-timer-progress, #question-timer-progress-outer, #question-timer-progress-inner').hide(); + // ensure inner shows empty state if something relies on it + $('#question-timer-progress-inner').css({ width: '0%', background: '#dc3545' }); + } catch (e) { + console.error('Error locking UI after htmx request', e); + } finally { + htmx.off('htmx:afterRequest', onAfter); + } + }; + + htmx.on('htmx:afterRequest', onAfter); + htmx.ajax('POST', window.location.href, { + values: { timed_out: '1' }, + swap: 'none', + headers: { + 'X-CSRFToken': document.querySelector('input[name="csrfmiddlewaretoken"]').value, + }, + target: "#timer-htmx-target", + }); } else { $timer.text(formatTime(remaining)); updateProgress(); diff --git a/atlas/views.py b/atlas/views.py index 136f0fc5..5c8af6b8 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -2786,11 +2786,33 @@ def collection_case_view_take( raise Http404("Self review not enabled") answer.completed = True + # If this was a timed-out submission, mark as completed + if request.POST.get('timed_out') == '1': + answer.completed = True + answer.save() cid_user_exam.end_time = timezone.now() cid_user_exam.save() + # If this was an AJAX/HTMX request, return JSON so the client + # can remain on the same page and update the UI without redirect. + # Detect HTMX/XHR requests robustly. HTMX sets the HX-Request header. + is_ajax = ( + request.headers.get('HX-Request', '').lower() == 'true' + or request.headers.get('x-requested-with') == 'XMLHttpRequest' + or getattr(request, 'htmx', False) + ) + if is_ajax: + from django.http import JsonResponse + return JsonResponse( + { + 'status': 'ok', + 'locked': answer.completed, + 'submitted_at': answer.submitted_at.isoformat() if getattr(answer, 'submitted_at', None) else None, + } + ) + if cid is not None: kwargs = {"pk": pk, "cid": cid, "passcode": passcode} redirect_url = "atlas:collection_case_view_take"