Enhance self-review functionality with new detail views, improved UI, and dynamic case management

This commit is contained in:
Ross
2026-04-27 11:43:15 +01:00
parent 95793c78c9
commit 36a77981c8
7 changed files with 398 additions and 84 deletions
@@ -1,78 +1,139 @@
{% extends 'base.html' %}
{% block content %}
{% if cid is not None %}
CID: {{cid}}
{% endif %}
<h2>Collection: {{collection.name}}
{% if collection.in_review_mode or cid_user_exam.completed %}
<span class="stamp-white">REVIEW</span>
{% endif %}
</h2>
{% if not collection.review_only %}
{{answer_count}} out of {{collection_length}} cases answered. Click to go to case.
{% endif %}
<div class="sba-finish-list">
<ul>
{% for question, answer, self_review in question_answer_tuples %}
<li> <span class="{% if not collection.review_only and not answer %}unanswered{% endif %}">
<a href="
{% if cid is not None %}
{% url 'atlas:collection_case_view_take' pk=collection.id case_number=forloop.counter0 cid=cid passcode=passcode %}
{% else %}
{% url 'atlas:collection_case_view_take_user' pk=collection.id case_number=forloop.counter0 %}
{% endif %}
">
Case: {{forloop.counter}}
{% if answer.completed or cid_user_exam.completed %}
<span title="You have completed this case"><i class="bi bi-check-square-fill"></i>
</span>
{% endif %}
{% if self_review %} <span class="self-review-complete" title="You have reviewed this case."> <i class="bi-card-checklist"></i></span>{% endif %}
</a>
<br/>
{% if not collection.review_only %}
{% if not answer %}
No answer
{% else %}
<details><summary>Answered</summary>
{% if aswer.answer %}
{{answer.answer}}
{% else %}
{{answer.json_answer}}
{% endif %}
</details>
{% endif %}
{% endif %}
</span>
</li>
{% endfor %}
</ul>
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
<div>
<h2 class="h4 mb-1">
{{ collection.name }}
{% if collection.in_review_mode or cid_user_exam.completed %}
<span class="badge bg-light text-dark ms-2">Review</span>
{% endif %}
</h2>
<div class="text-muted small">
{% if cid is not None %}
CID: <strong>{{ cid }}</strong>
{% else %}
Your collection overview
{% endif %}
</div>
</div>
<div class="card border-secondary" style="min-width: 280px;">
<div class="card-body py-2 px-3">
<div class="small text-muted">Progress</div>
{% if not collection.review_only %}
<div class="fw-semibold">{{ answer_count }} / {{ collection_length }} answered</div>
<div class="progress mt-2" style="height: 8px;">
<div class="progress-bar" role="progressbar" style="width: {% widthratio answer_count collection_length 100 %}%"></div>
</div>
{% else %}
<div class="fw-semibold">Review-only collection</div>
{% endif %}
<div class="mt-2 small">Started: {{ cid_user_exam.start_time }}</div>
</div>
</div>
</div>
<div class="list-group mb-3">
{% for question, answer, self_review in question_answer_tuples %}
<div class="list-group-item {% if not answer %}bg-danger-subtle border-danger-subtle{% endif %}">
<div class="d-flex flex-wrap justify-content-between align-items-start gap-2">
<div>
<div class="fw-semibold">
Case {{ forloop.counter }}{% if collection.show_title_post %}: {{ question.case.title|default:question.case.pk|truncatechars:70 }}{% endif %}
{% if answer and answer.completed or cid_user_exam.completed %}
<span class="badge bg-success-subtle text-success-emphasis ms-2">Completed</span>
{% elif answer %}
<span class="badge bg-warning-subtle text-warning-emphasis ms-2">Saved</span>
{% else %}
<span class="badge bg-danger-subtle text-danger-emphasis ms-2">Not answered</span>
{% endif %}
{% if self_review %}
<span class="badge bg-info-subtle text-info-emphasis ms-1">Self review added</span>
{% endif %}
</div>
{% if collection.show_title_post %}
<div class="small text-muted">
{{ question.case }}
</div>
{% endif %}
</div>
<div class="d-flex flex-wrap gap-2">
<a class="btn btn-sm btn-primary"
href="{% if cid is not None %}{% url 'atlas:collection_case_view_take' pk=collection.id case_number=forloop.counter0 cid=cid passcode=passcode %}{% else %}{% url 'atlas:collection_case_view_take_user' pk=collection.id case_number=forloop.counter0 %}{% endif %}">
Open Case
</a>
{% if cid is None %}
<a class="btn btn-sm btn-outline-secondary"
href="{% url 'atlas:collection_case_view_take_user_answers' pk=collection.id case_number=forloop.counter0 %}">
View Answer / Report
</a>
{% else %}
<a class="btn btn-sm btn-outline-secondary"
href="{% url 'atlas:collection_case_view_take' pk=collection.id case_number=forloop.counter0 cid=cid passcode=passcode %}">
View Answer / Report
</a>
{% endif %}
<a class="btn btn-sm btn-outline-info"
href="{% url 'atlas:add_self_review' cid_user_exam.id question.case.id %}">
{% if self_review %}Add New Self Review{% else %}Add Self Review{% endif %}
</a>
</div>
</div>
Start time: {{cid_user_exam.start_time}}
{% if self_review %}
<div class="mt-2 pt-2 border-top">
<div class="small fw-semibold mb-1">Your self reviews</div>
<div class="d-flex flex-wrap gap-2">
{% for review in self_review %}
<a class="btn btn-sm btn-outline-info"
href="{% url 'atlas:self_review_detail' cid_user_exam.id question.case.id review.id %}">
View review {{ forloop.counter }}
</a>
<a class="btn btn-sm btn-outline-warning"
href="{% url 'atlas:self_review_update' cid_user_exam.id question.case.id review.id %}">
Edit review {{ forloop.counter }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if not collection.review_only %}
{% if answer %}
<details class="mt-2">
<summary class="small text-muted">Preview saved answer/report</summary>
<div class="small mt-2 p-2 bg-body-tertiary rounded">
{% if answer.answer %}
{{ answer.answer|linebreaksbr }}
{% else %}
{{ answer.json_answer }}
{% endif %}
</div>
</details>
{% endif %}
{% endif %}
</div>
{% endfor %}
</div>
{% if not collection.review_only and not cid_user_exam.completed %}
<p>Completed: <span id="completed-state">{{cid_user_exam.completed}}</span></p>
<form hx-post=""
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete
hx-confirm="Finish session?"
hx-target="#completed-state"
_="on htmx:afterOnLoad remove me"
>
<button type="submit" name="finish" value="finish">Finish</button>
</form>
<div class="card border-warning-subtle">
<div class="card-body d-flex flex-wrap justify-content-between align-items-center gap-2">
<div>
<div class="fw-semibold">Ready to submit this session?</div>
<div class="small text-muted">Completed: <span id="completed-state">{{ cid_user_exam.completed }}</span></div>
</div>
<form hx-post=""
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete
hx-confirm="Finish session?"
hx-target="#completed-state"
_="on htmx:afterOnLoad remove me"
class="m-0">
<button type="submit" name="finish" value="finish" class="btn btn-warning">Finish Session</button>
</form>
</div>
</div>
{% endif %}
@@ -1,11 +1,31 @@
{% for cd in casedetails %}
{% with idx=forloop.counter0 %}
{% if cid %}
<a class="dropdown-item {% if idx == current %}active{% endif %}" href="{% url 'atlas:collection_case_view_take' pk=collection.pk case_number=idx cid=cid passcode=passcode %}">
{% else %}
<a class="dropdown-item {% if idx == current %}active{% endif %}" href="{% url 'atlas:collection_case_view_take_user' pk=collection.pk case_number=idx %}">
{% endif %}
Case {{ forloop.counter }}: {{ cd.case.title|default:cd.case.pk|truncatechars:60 }}
</a>
{% endwith %}
{% for item in jump_items %}
<div class="dropdown-item-text {% if not item.has_answer %}bg-danger-subtle border border-danger-subtle rounded{% elif item.idx == current %}bg-primary-subtle rounded{% endif %} py-2 px-2 mb-1">
<div class="d-flex justify-content-between align-items-start gap-2">
<a class="text-decoration-none small fw-semibold"
href="{{ item.case_url }}">
Case {{ item.idx|add:1 }}: {{ item.cd.case.title|default:item.cd.case.pk|truncatechars:45 }}
</a>
<div class="d-flex gap-1">
{% if item.has_answer %}
<span class="badge bg-success-subtle text-success-emphasis">Answered</span>
{% else %}
<span class="badge bg-danger-subtle text-danger-emphasis">Not answered</span>
{% endif %}
{% if item.has_self_review %}
<span class="badge bg-info-subtle text-info-emphasis">Self review</span>
{% endif %}
</div>
</div>
<div class="d-flex flex-wrap gap-1 mt-2">
<a class="btn btn-outline-secondary btn-sm py-0 px-2" href="{{ item.answer_url }}">Answer</a>
{% if item.self_review_view_url %}
<a class="btn btn-outline-info btn-sm py-0 px-2" href="{{ item.self_review_view_url }}">View review</a>
<a class="btn btn-outline-warning btn-sm py-0 px-2" href="{{ item.self_review_edit_url }}">Edit</a>
{% else %}
<a class="btn btn-outline-info btn-sm py-0 px-2" href="{{ item.self_review_add_url }}">Add review</a>
{% endif %}
</div>
</div>
{% empty %}
<div class="dropdown-item-text text-muted small px-2 py-2">No cases available.</div>
{% endfor %}
@@ -24,20 +24,30 @@
<div class="small text-muted">
Updated: {{ review.review_update_date|date:"Y-m-d H:i" }}
</div>
<div class="btn-group btn-group-sm" role="group" aria-label="Edit self review">
<a class="btn btn-outline-primary"
<div class="d-flex flex-wrap gap-1" role="group" aria-label="Self review actions">
<a class="btn btn-outline-primary btn-sm"
href="{% url 'atlas:self_review_detail' cid_user_exam.id case.id review.id %}">
View
</a>
<button type="button" class="btn btn-outline-info btn-sm"
onclick="openSelfReviewPopup('{% url 'atlas:self_review_detail' cid_user_exam.id case.id review.id %}')">
View popup
</button>
<button type="button" class="btn btn-outline-secondary btn-sm"
onclick="openSelfReviewSidebar('{% url 'atlas:self_review_detail' cid_user_exam.id case.id review.id %}')">
View sidebar
</button>
<a class="btn btn-outline-warning btn-sm"
href="{% url 'atlas:self_review_update' cid_user_exam.id case.id review.id %}">
Edit
</a>
<button type="button"
class="btn btn-outline-info"
<button type="button" class="btn btn-outline-warning btn-sm"
onclick="openSelfReviewPopup('{% url 'atlas:self_review_update' cid_user_exam.id case.id review.id %}')">
Popup
Edit popup
</button>
<button type="button"
class="btn btn-outline-secondary"
<button type="button" class="btn btn-outline-warning btn-sm"
onclick="openSelfReviewSidebar('{% url 'atlas:self_review_update' cid_user_exam.id case.id review.id %}')">
Sidebar
Edit sidebar
</button>
</div>
</div>
@@ -0,0 +1,52 @@
<div class="p-3">
<div class="card border-secondary shadow-sm mb-2">
<div class="card-header d-flex justify-content-between align-items-center">
<h2 class="h6 mb-0">Self Review</h2>
<span class="badge bg-primary-subtle text-primary-emphasis">{{ user_exam.exam }}</span>
</div>
<div class="card-body">
<div class="small text-muted mb-2">Case: <strong>{{ case }}</strong></div>
<div class="row g-2 mb-3">
<div class="col-6">
<div class="p-2 border rounded">
<div class="small text-muted">Findings</div>
<div class="fw-semibold">{{ review.findings|default:"-" }}/5</div>
</div>
</div>
<div class="col-6">
<div class="p-2 border rounded">
<div class="small text-muted">Interpretation</div>
<div class="fw-semibold">{{ review.interpretation|default:"-" }}/5</div>
</div>
</div>
</div>
<div class="mb-3">
<div class="small text-muted mb-1">Comments</div>
<div class="p-2 border rounded bg-body-tertiary">
{% if review.comments %}
{{ review.comments|linebreaksbr }}
{% else %}
<span class="text-muted">No comments added.</span>
{% endif %}
</div>
</div>
<div class="small text-muted mb-3">
Updated: {{ review.review_update_date }}
</div>
<div class="d-flex gap-2 flex-wrap">
<a class="btn btn-outline-warning btn-sm"
href="{% url 'atlas:self_review_update' user_exam.id case.id review.id %}?embed=1">
Edit in sidebar
</a>
<a class="btn btn-outline-secondary btn-sm"
href="{% url 'atlas:self_review_detail' user_exam.id case.id review.id %}">
Open full page
</a>
</div>
</div>
</div>
</div>
@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block content %}
<div class="container px-0" style="max-width: 920px;">
<div class="card border-secondary shadow-sm mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">Self Review</h2>
<span class="badge bg-primary-subtle text-primary-emphasis">{{ user_exam.exam }}</span>
</div>
<div class="card-body">
<div class="small text-muted mb-3">Case: <strong>{{ case }}</strong></div>
<div class="row g-2 mb-3">
<div class="col-sm-6">
<div class="p-2 border rounded">
<div class="small text-muted">Findings</div>
<div class="fw-semibold">{{ review.findings|default:"-" }}/5</div>
</div>
</div>
<div class="col-sm-6">
<div class="p-2 border rounded">
<div class="small text-muted">Interpretation</div>
<div class="fw-semibold">{{ review.interpretation|default:"-" }}/5</div>
</div>
</div>
</div>
<div class="mb-3">
<div class="small text-muted mb-1">Comments</div>
<div class="p-2 border rounded bg-body-tertiary">
{% if review.comments %}
{{ review.comments|linebreaksbr }}
{% else %}
<span class="text-muted">No comments added.</span>
{% endif %}
</div>
</div>
<div class="small text-muted mb-3">
Created: {{ review.review_date }}<br>
Updated: {{ review.review_update_date }}
</div>
<div class="d-flex gap-2 flex-wrap">
<a class="btn btn-outline-warning"
href="{% url 'atlas:self_review_update' user_exam.id case.id review.id %}">
Edit Self Review
</a>
<a class="btn btn-outline-secondary" href="{{ review.get_absolute_url }}">
Back to case
</a>
</div>
</div>
</div>
</div>
{% endblock %}