feat: Enhance collection history page with detailed user and CID exam statistics
This commit is contained in:
@@ -1,67 +1,162 @@
|
||||
{% extends 'atlas/exams.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<h2 class="mb-0">{{collection.name}}</h2>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:user_messages_inbox' %}">Global Messages Inbox</a>
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
|
||||
<div>
|
||||
<h2 class="h4 mb-1">{{ collection.name }}</h2>
|
||||
<div class="text-muted small">Collection attempt history and message activity</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:collection_detail' collection.pk %}">Collection Detail</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:user_messages_inbox' %}">Global Messages Inbox</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card border-secondary h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="small text-muted">Cases</div>
|
||||
<div class="h4 mb-0">{{ total_cases }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card border-secondary h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="small text-muted">Attempts</div>
|
||||
<div class="h4 mb-0">{{ total_attempt_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card border-secondary h-100">
|
||||
<div class="card-body py-3">
|
||||
<div class="small text-muted">Completed Attempts</div>
|
||||
<div class="h4 mb-0">{{ completed_attempt_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card border-secondary h-100 {% if total_outstanding_messages %}border-danger-subtle{% endif %}">
|
||||
<div class="card-body py-3">
|
||||
<div class="small text-muted">Outstanding Messages</div>
|
||||
<div class="h4 mb-0 {% if total_outstanding_messages %}text-danger{% endif %}">{{ total_outstanding_messages }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if userexams %}
|
||||
<h3>Users</h3>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h3 class="h5 mb-0">Users</h3>
|
||||
<div class="small text-muted">{{ userexams|length }} user attempt{{ userexams|length|pluralize }}</div>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<div class="d-flex flex-column gap-3 mb-4">
|
||||
{% for userexam in userexams %}
|
||||
<li id="user-history-{{ userexam.user_user.pk }}">
|
||||
<b><a href="{% url 'atlas:collection_history_user' collection.pk userexam.user_user.pk %}">{{userexam.get_user_name}}</a><b><br/>
|
||||
Completed: {{userexam.completed}}<br/>
|
||||
Started: {{userexam.start_time}}, Ended: {{userexam.end_time}}<br/>
|
||||
{% if userexam.outstanding_feedback_count %}
|
||||
<span class="badge bg-danger">{{ userexam.outstanding_feedback_count }} outstanding feedback</span><br/>
|
||||
{% elif userexam.total_review_message_count %}
|
||||
<span class="badge bg-secondary">{{ userexam.total_review_message_count }} conversation message{{ userexam.total_review_message_count|pluralize }}</span><br/>
|
||||
{% endif %}
|
||||
|
||||
<button
|
||||
hx-post="{% url 'atlas:collection_reset_answers_user' collection.pk userexam.user_user.pk %}"
|
||||
hx-target="#user-history-{{ userexam.user_user.pk }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this user's collection history?"
|
||||
class="btn btn-danger btn-sm remove-button">
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
<div id="user-history-{{ userexam.user_user.pk }}" class="card {% if userexam.outstanding_feedback_count %}border-danger-subtle{% else %}border-secondary-subtle{% endif %}">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
||||
<div>
|
||||
<div class="fw-semibold">
|
||||
<a href="{% url 'atlas:collection_history_user' collection.pk userexam.user_user.pk %}">{{ userexam.get_user_name }}</a>
|
||||
{% if userexam.completed %}
|
||||
<span class="badge bg-success-subtle text-success-emphasis ms-2">Completed</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis ms-2">In progress</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="small text-muted mt-1">Started: {{ userexam.start_time|default:"-" }}</div>
|
||||
<div class="small text-muted">Ended: {{ userexam.end_time|default:"-" }}</div>
|
||||
<div class="mt-2 d-flex flex-wrap gap-2">
|
||||
{% if userexam.outstanding_feedback_count %}
|
||||
<span class="badge bg-danger">{{ userexam.outstanding_feedback_count }} outstanding message{{ userexam.outstanding_feedback_count|pluralize }}</span>
|
||||
{% endif %}
|
||||
{% if userexam.total_review_message_count %}
|
||||
<span class="badge bg-secondary">{{ userexam.total_review_message_count }} conversation message{{ userexam.total_review_message_count|pluralize }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-dark-subtle text-body-secondary">No conversation yet</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm btn-primary" href="{% url 'atlas:collection_history_user' collection.pk userexam.user_user.pk %}">Open Attempt</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:collection_user_messages' collection.pk userexam.user_user.pk %}">Messages</a>
|
||||
<button
|
||||
hx-post="{% url 'atlas:collection_reset_answers_user' collection.pk userexam.user_user.pk %}"
|
||||
hx-target="#user-history-{{ userexam.user_user.pk }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this user's collection history?"
|
||||
class="btn btn-danger btn-sm remove-button">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if cidexams %}
|
||||
<h3>CID Users</h3>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h3 class="h5 mb-0">CID Users</h3>
|
||||
<div class="small text-muted">{{ cidexams|length }} CID attempt{{ cidexams|length|pluralize }}</div>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<div class="d-flex flex-column gap-3">
|
||||
{% for cidexam in cidexams %}
|
||||
<li id="cid-history-{{ cidexam.cid_user.pk }}">
|
||||
<b><a href="{% url 'atlas:collection_history_ciduser' collection.pk cidexam.cid_user.cid %}">{{cidexam.cid_user}}</a><b><br/>
|
||||
Completed: {{cidexam.completed}}<br/>
|
||||
Started: {{cidexam.start_time}}, Ended: {{cidexam.end_time}}<br/>
|
||||
{% if cidexam.outstanding_feedback_count %}
|
||||
<span class="badge bg-danger">{{ cidexam.outstanding_feedback_count }} outstanding feedback</span><br/>
|
||||
{% elif cidexam.total_review_message_count %}
|
||||
<span class="badge bg-secondary">{{ cidexam.total_review_message_count }} conversation message{{ cidexam.total_review_message_count|pluralize }}</span><br/>
|
||||
{% endif %}
|
||||
|
||||
<button
|
||||
hx-post="{% url 'atlas:collection_reset_answers_ciduser' collection.pk cidexam.cid_user.cid %}"
|
||||
hx-target="#cid-history-{{ cidexam.cid_user.cid }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this CID user's collection history?"
|
||||
class="btn btn-danger btn-sm remove-button">
|
||||
Delete
|
||||
</button>
|
||||
</li>
|
||||
<div id="cid-history-{{ cidexam.cid_user.cid }}" class="card {% if cidexam.outstanding_feedback_count %}border-danger-subtle{% else %}border-secondary-subtle{% endif %}">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3">
|
||||
<div>
|
||||
<div class="fw-semibold">
|
||||
<a href="{% url 'atlas:collection_history_ciduser' collection.pk cidexam.cid_user.cid %}">{{ cidexam.cid_user }}</a>
|
||||
{% if cidexam.completed %}
|
||||
<span class="badge bg-success-subtle text-success-emphasis ms-2">Completed</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis ms-2">In progress</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="small text-muted mt-1">Started: {{ cidexam.start_time|default:"-" }}</div>
|
||||
<div class="small text-muted">Ended: {{ cidexam.end_time|default:"-" }}</div>
|
||||
<div class="mt-2 d-flex flex-wrap gap-2">
|
||||
{% if cidexam.outstanding_feedback_count %}
|
||||
<span class="badge bg-danger">{{ cidexam.outstanding_feedback_count }} outstanding message{{ cidexam.outstanding_feedback_count|pluralize }}</span>
|
||||
{% endif %}
|
||||
{% if cidexam.total_review_message_count %}
|
||||
<span class="badge bg-secondary">{{ cidexam.total_review_message_count }} conversation message{{ cidexam.total_review_message_count|pluralize }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-dark-subtle text-body-secondary">No conversation yet</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-sm btn-primary" href="{% url 'atlas:collection_history_ciduser' collection.pk cidexam.cid_user.cid %}">Open Attempt</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="{% url 'atlas:collection_cid_messages' collection.pk cidexam.cid_user.cid %}">Messages</a>
|
||||
<button
|
||||
hx-post="{% url 'atlas:collection_reset_answers_ciduser' collection.pk cidexam.cid_user.cid %}"
|
||||
hx-target="#cid-history-{{ cidexam.cid_user.cid }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this CID user's collection history?"
|
||||
class="btn btn-danger btn-sm remove-button">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not userexams and not cidexams %}
|
||||
<div class="card border-secondary">
|
||||
<div class="card-body text-muted small">No attempts have been recorded for this collection yet.</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
+42
-25
@@ -4095,35 +4095,48 @@ def collection_history(request, exam_id: int):
|
||||
"""An overview page that shows who has attempted / finised the collection"""
|
||||
collection = get_object_or_404(CaseCollection, pk=exam_id)
|
||||
|
||||
casedetails = CaseDetail.objects.filter(collection=collection).order_by(
|
||||
"sort_order"
|
||||
casedetails = list(CaseDetail.objects.filter(collection=collection).order_by("sort_order"))
|
||||
|
||||
userexams = list(
|
||||
collection.get_user_exams()
|
||||
.select_related("user_user")
|
||||
.annotate(
|
||||
outstanding_feedback_count=Count(
|
||||
"case_review_messages",
|
||||
filter=Q(
|
||||
case_review_messages__requires_acknowledgement=True,
|
||||
case_review_messages__acknowledged_at__isnull=True,
|
||||
),
|
||||
distinct=True,
|
||||
),
|
||||
total_review_message_count=Count("case_review_messages", distinct=True),
|
||||
)
|
||||
.order_by("-outstanding_feedback_count", "completed", "-start_time")
|
||||
)
|
||||
cidexams = list(
|
||||
collection.get_cid_exams()
|
||||
.select_related("cid_user")
|
||||
.annotate(
|
||||
outstanding_feedback_count=Count(
|
||||
"case_review_messages",
|
||||
filter=Q(
|
||||
case_review_messages__requires_acknowledgement=True,
|
||||
case_review_messages__acknowledged_at__isnull=True,
|
||||
),
|
||||
distinct=True,
|
||||
),
|
||||
total_review_message_count=Count("case_review_messages", distinct=True),
|
||||
)
|
||||
.order_by("-outstanding_feedback_count", "completed", "-start_time")
|
||||
)
|
||||
|
||||
userexams = collection.get_user_exams().annotate(
|
||||
outstanding_feedback_count=Count(
|
||||
"case_review_messages",
|
||||
filter=Q(
|
||||
case_review_messages__requires_acknowledgement=True,
|
||||
case_review_messages__acknowledged_at__isnull=True,
|
||||
),
|
||||
distinct=True,
|
||||
),
|
||||
total_review_message_count=Count("case_review_messages", distinct=True),
|
||||
total_outstanding_messages = sum(item.outstanding_feedback_count for item in userexams) + sum(
|
||||
item.outstanding_feedback_count for item in cidexams
|
||||
)
|
||||
cidexams = collection.get_cid_exams().annotate(
|
||||
outstanding_feedback_count=Count(
|
||||
"case_review_messages",
|
||||
filter=Q(
|
||||
case_review_messages__requires_acknowledgement=True,
|
||||
case_review_messages__acknowledged_at__isnull=True,
|
||||
),
|
||||
distinct=True,
|
||||
),
|
||||
total_review_message_count=Count("case_review_messages", distinct=True),
|
||||
completed_attempt_count = sum(1 for item in userexams if item.completed) + sum(
|
||||
1 for item in cidexams if item.completed
|
||||
)
|
||||
|
||||
print("useruserexams:", userexams)
|
||||
print("cidexams:", cidexams)
|
||||
total_attempt_count = len(userexams) + len(cidexams)
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -4134,6 +4147,10 @@ def collection_history(request, exam_id: int):
|
||||
"can_edit": True,
|
||||
"userexams": userexams,
|
||||
"cidexams": cidexams,
|
||||
"total_cases": len(casedetails),
|
||||
"total_attempt_count": total_attempt_count,
|
||||
"completed_attempt_count": completed_attempt_count,
|
||||
"total_outstanding_messages": total_outstanding_messages,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user