Add timestamps for answer tracking in report models and views; enhance timer display in templates
This commit is contained in:
@@ -20,6 +20,11 @@
|
||||
<div id="question-timer-block" style="margin-top:8px; margin-bottom:8px;" title="This question has a time limit of {{ collection.question_time_limit }} seconds. The timer will start when the page loads.">
|
||||
<strong>Time remaining:</strong>
|
||||
<span id="question-timer" aria-live="polite"> </span>
|
||||
<div id="question-timer-progress" style="display:inline-block; vertical-align: middle; width: 200px; margin-left:12px;">
|
||||
<div id="question-timer-progress-outer" style="background:#e9ecef; border-radius:6px; height:10px; overflow:hidden;">
|
||||
<div id="question-timer-progress-inner" style="width:100%; height:100%; background:var(--timer-color, #28a745);"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -338,12 +343,12 @@
|
||||
try {
|
||||
var timeLimit = {{ collection.question_time_limit|default:'null' }};
|
||||
var questionCompleted = {{ question_completed|yesno:"true,false" }};
|
||||
var answerStartedAtIso = "{{ answer_started_at_iso|default:'null' }}";
|
||||
console.debug('Timer init:', {timeLimit: timeLimit, questionCompleted: questionCompleted});
|
||||
|
||||
if (!timeLimit || questionCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $timer = $('#question-timer');
|
||||
var $form = $('form.post-form');
|
||||
|
||||
@@ -352,7 +357,19 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute remaining based on the canonical start time (if provided)
|
||||
var remaining = parseInt(timeLimit, 10);
|
||||
if (answerStartedAtIso) {
|
||||
try {
|
||||
var started = new Date(answerStartedAtIso);
|
||||
var now = new Date();
|
||||
var elapsed = Math.floor((now - started) / 1000);
|
||||
remaining = Math.max(0, remaining - elapsed);
|
||||
console.debug('Timer: using started_at, elapsed seconds:', elapsed, 'remaining:', remaining);
|
||||
} catch (e) {
|
||||
console.debug('Timer: invalid started_at iso, falling back to full timeLimit', e);
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(s) {
|
||||
var mins = Math.floor(s / 60);
|
||||
@@ -360,13 +377,40 @@
|
||||
return mins + ':' + (secs < 10 ? '0' + secs : secs);
|
||||
}
|
||||
|
||||
var $progressInner = $('#question-timer-progress-inner');
|
||||
$timer.text(formatTime(remaining));
|
||||
|
||||
function updateProgress() {
|
||||
var pct = Math.max(0, Math.min(100, Math.round((remaining / timeLimit) * 100)));
|
||||
var widthPct = pct;
|
||||
$progressInner.css('width', widthPct + '%');
|
||||
|
||||
// Smooth color transition: green (120) -> orange (30) -> red (0)
|
||||
// We use a two-stage interpolation so the midpoint (~50%) is orange.
|
||||
var hue = 0;
|
||||
if (pct > 50) {
|
||||
// interpolate from orange (30) to green (120)
|
||||
var t = (pct - 50) / 50.0; // 0..1
|
||||
hue = 30 + t * (120 - 30);
|
||||
} else {
|
||||
// interpolate from red (0) to orange (30)
|
||||
var t = pct / 50.0; // 0..1
|
||||
hue = 0 + t * (30 - 0);
|
||||
}
|
||||
|
||||
var color = 'hsl(' + Math.round(hue) + ', 75%, 40%)';
|
||||
$progressInner.css('background', color);
|
||||
}
|
||||
|
||||
updateProgress();
|
||||
|
||||
var intervalId = setInterval(function () {
|
||||
remaining -= 1;
|
||||
if (remaining <= 0) {
|
||||
clearInterval(intervalId);
|
||||
$timer.text('0:00');
|
||||
$progressInner.css('width', '0%');
|
||||
$progressInner.css('background', '#dc3545');
|
||||
var $next = $form.find('button[name="next"]');
|
||||
var $finish = $form.find('button[name="finish"]');
|
||||
|
||||
@@ -381,6 +425,7 @@
|
||||
}
|
||||
} else {
|
||||
$timer.text(formatTime(remaining));
|
||||
updateProgress();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user