Enhance submitted candidates display by adding dynamic exam status indicators and improving badge tooltip functionality for better user feedback.

This commit is contained in:
Ross
2026-01-05 11:30:53 +00:00
parent 96e88c05a4
commit 378d784435
3 changed files with 48 additions and 34 deletions
+25 -30
View File
@@ -174,52 +174,47 @@
}catch(e){ console.error('highlightSubmitted collect error', e); }
});
// Add badges for CID list items
// Helper to ensure a single badge exists inside an li and update its tooltip
function ensureSubmittedBadge(li, title){
let b = li.querySelector('.submitted-badge');
if(!b){
b = document.createElement('span');
b.className = 'submitted-badge badge bg-success ms-2';
b.textContent = 'Submitted';
li.appendChild(b);
}
if(title){
b.setAttribute('title', title);
b.setAttribute('data-bs-toggle', 'tooltip');
}
return b;
}
// Add/update badges for CID list items
if(submittedCids.size){
document.querySelectorAll('#cid-list .list-group-item').forEach(function(li){
try{
const link = li.querySelector && li.querySelector('.cid-link');
const text = (link && link.textContent) ? link.textContent.trim() : (li.textContent||'').trim();
if(submittedCids.has(text) && !li.querySelector('.submitted-badge')){
if(submittedCids.has(text)){
const info = submittedCids.get(text) || {first:'', last:''};
const badge = document.createElement('span');
badge.className = 'submitted-badge badge bg-success ms-2';
badge.textContent = 'Submitted';
if(info.first || info.last){
const title = `Submitted\nFirst: ${info.first}\nLast: ${info.last}`;
badge.setAttribute('title', title);
badge.setAttribute('data-bs-toggle', 'tooltip');
}
if(link && link.parentNode){
link.parentNode.appendChild(badge);
} else {
li.appendChild(badge);
}
const title = (info.first || info.last) ? `Submitted\nFirst: ${info.first}\nLast: ${info.last}` : '';
ensureSubmittedBadge(li, title);
}
}catch(e){ console.error('highlightSubmitted CID item error', e); }
});
}
// Add badges for User list items
// Add/update badges for User list items
if(submittedUsers.size){
document.querySelectorAll('#user-list .list-group-item').forEach(function(li){
try{
const txt = (li.textContent||'').trim();
for(const user of submittedUsers){
for(const user of submittedUsers.keys()){
if(txt.indexOf(user) !== -1){
if(!li.querySelector('.submitted-badge')){
const info = submittedUsers.get(user) || {first:'', last:''};
const badge = document.createElement('span');
badge.className = 'submitted-badge badge bg-success ms-2';
badge.textContent = 'Submitted';
if(info.first || info.last){
const title = `Submitted\nFirst: ${info.first}\nLast: ${info.last}`;
badge.setAttribute('title', title);
badge.setAttribute('data-bs-toggle', 'tooltip');
}
const nameEl = li.querySelector('.fw-bold') || li.querySelector('a') || li;
nameEl.appendChild(badge);
}
const info = submittedUsers.get(user) || {first:'', last:''};
const title = (info.first || info.last) ? `Submitted\nFirst: ${info.first}\nLast: ${info.last}` : '';
ensureSubmittedBadge(li, title);
break;
}
}
@@ -23,11 +23,11 @@
</thead>
<tbody id="submitted-candidates">
{% for user_data in user_exam_data %}
<tr data-cid="{{ user_data.cid_user }}" data-user="{{ user_data.user_user }}" data-first="{{ user_data.start_time|default_if_none:'' }}" data-last="{{ user_data.end_time|default_if_none:'' }}">
<td class="fw-bold">{{ user_data.cid_user }}</td>
<tr data-cid="{{ user_data.cid_user }}" data-user="{{ user_data.user_user }}" data-first="{{ user_data.start_time|default_if_none:'' }}" data-last="{{ user_data.end_time|default_if_none:'' }}" {% if not user_data.in_exam %}class="table-warning"{% endif %}>
<td class="fw-bold">{{ user_data.cid_user }}{% if not user_data.in_exam %} <span class="badge bg-danger ms-2" title="Candidate/User not added to exam">Not added</span>{% endif %}</td>
<td>{{ user_data.user_user }}</td>
<td class="text-nowrap small text-muted">{{ user_data.start_time }}</td>
<td class="text-nowrap small text-muted">{{ user_data.end_time }}</td>
<td class="text-nowrap small {% if user_data.in_exam %}text-muted{% else %}text-dark{% endif %}">{{ user_data.start_time }}</td>
<td class="text-nowrap small {% if user_data.in_exam %}text-muted{% else %}text-dark{% endif %}">{{ user_data.end_time }}</td>
<td class="text-end">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-secondary" title="View details" hx-get="{% url exam.get_app_name|add:':exam_user_status' exam.id %}?cid={{ user_data.cid_user }}" hx-target="#history-modal .modal-body" data-bs-toggle="modal" data-bs-target="#history-modal"><i class="bi bi-eye"></i></button>
+19
View File
@@ -2347,6 +2347,25 @@ class ExamViews(View, LoginRequiredMixin):
.order_by("start_time")
)
# Determine which submitted entries correspond to candidates/users
# that are actually added to the exam so we can highlight those that
# are not. Collect valid IDs once to avoid per-row queries.
valid_cid_pks = set(exam.valid_cid_users.values_list("pk", flat=True))
valid_user_pks = set(exam.valid_user_users.values_list("pk", flat=True))
for ue in user_exam_data:
in_exam = False
try:
if ue.cid_user_id and ue.cid_user_id in valid_cid_pks:
in_exam = True
if ue.user_user_id and ue.user_user_id in valid_user_pks:
in_exam = True
except Exception:
in_exam = False
# Attach a dynamic attribute the template can read
setattr(ue, "in_exam", in_exam)
return render(
request,
"generic/partials/exam_cids_submitted_list.html",