feat: Implement responsive design enhancements with new utility classes and improved layout for various components

This commit is contained in:
Ross
2026-05-11 13:31:25 +01:00
parent d2a4ee9f70
commit 59956670df
15 changed files with 236 additions and 31 deletions
@@ -104,6 +104,17 @@
}
.quick-edit-wrap .quick-edit-button:hover { opacity: 1; }
@media (max-width: 767.98px) {
.quick-edit-wrap .quick-edit-button,
.case-meta-item .quick-edit-button,
.case-inline-field .quick-edit-compact {
opacity: 0.95;
pointer-events: auto;
padding: 0.15rem 0.35rem;
font-size: 0.9rem;
}
}
/* Metadata-specific quick-edit: ensure edit button overlays without moving badges */
.case-meta-item { position: relative; }
.case-meta-item .quick-edit-button {
@@ -6,8 +6,7 @@
{% include 'atlas/partials/_viewing_case_as_part_of_collection.html' with nav_link_view="atlas:collection_case_displaysetup" %}
<h2>Setup default display for case</h2>
<div id="root" class="dicom-viewer-root" data-images="{{casedetail.case.get_series_images_nested}}"
style="box-sizing: border-box; background: #222; width: 100vw; height: 800px; max-width: 1600px;"
<div id="root" class="dicom-viewer-root viewer-frame-setup" data-images="{{casedetail.case.get_series_images_nested}}"
data-auto-cache-stack="true"
{% if casedetail.default_viewerstate %}
data-viewerstate={{ casedetail.default_viewerstate }}
@@ -40,8 +40,8 @@
<div id="question-timer-block" class="mb-3" title="This case has a view time limit of {{ effective_question_time_limit }} seconds.">
<div class="d-flex align-items-center gap-3">
<div><strong>Case view:</strong> <span id="question-timer" aria-live="polite">&nbsp;</span></div>
<div style="flex:1; max-width:320px;">
<div class="progress" style="height:10px;">
<div class="flex-max-320">
<div class="progress progress-compact-sm">
<div id="question-timer-progress-inner" class="progress-bar" role="progressbar" style="width:100%; background:var(--timer-color, #28a745);"></div>
</div>
</div>
@@ -105,9 +105,9 @@
<div>
{% if question_completed %}
<iframe id="viewer" style="width: 100%; height: 100%; border: none; padding: 0px; min-height: 700px" src="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% if case_number %}{% url 'atlas:collection_case_dicom_json_review' collection.pk case_number %}{% else %}{% url 'atlas:collection_case_dicom_json_review_legacy' collection.pk case.pk %}{% endif %}"></iframe>
<iframe id="viewer" class="viewer-frame-ohif" src="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% if case_number %}{% url 'atlas:collection_case_dicom_json_review' collection.pk case_number %}{% else %}{% url 'atlas:collection_case_dicom_json_review_legacy' collection.pk case.pk %}{% endif %}"></iframe>
{% else %}
<iframe id="viewer" style="width: 100%; height: 100%; border: none; padding: 0px; min-height: 700px" src="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% if case_number %}{% url 'atlas:collection_case_dicom_json' collection.pk case_number %}{% else %}{% url 'atlas:collection_case_dicom_json_legacy' collection.pk case.pk %}{% endif %}"></iframe>
<iframe id="viewer" class="viewer-frame-ohif" src="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% if case_number %}{% url 'atlas:collection_case_dicom_json' collection.pk case_number %}{% else %}{% url 'atlas:collection_case_dicom_json_legacy' collection.pk case.pk %}{% endif %}"></iframe>
{% endif %}
</div>
@@ -128,8 +128,7 @@
<details id="dicom-viewer-details" open>
<summary>Viewer</summary>
<div id="main_viewer" class="dicom-viewer-root"
style="box-sizing: border-box; background: #222; width: 100%; height: 600px;"
<div id="main_viewer" class="dicom-viewer-root viewer-frame-standard"
data-auto-cache-stack="false"
data-named-stacks='{{casedetail.get_case_named_stacks}}'
></div>
@@ -12,7 +12,7 @@
{% endif %}
</div>
</div>
<div class="card border-secondary" style="min-width: 320px;">
<div class="card border-secondary summary-card">
<div class="card-body py-2 px-3">
<div class="small text-muted">Feedback Summary</div>
<div class="fw-semibold">{{ total_outstanding_feedback }} outstanding feedback item{{ total_outstanding_feedback|pluralize }}</div>
@@ -6,11 +6,11 @@
<h2 class="h4 mb-1">{{ collection.name }}</h2>
<div class="text-muted small">Learner: <strong>{{ user }}</strong></div>
</div>
<div class="card border-secondary" style="min-width: 320px;">
<div class="card border-secondary summary-card">
<div class="card-body py-2 px-3">
<div class="small text-muted">Attempt Progress</div>
<div class="fw-semibold">{{ answer_count }} / {{ collection_length }} answered</div>
<div class="progress mt-2" style="height: 8px;">
<div class="progress progress-compact mt-2">
<div class="progress-bar" role="progressbar" style="width: {% widthratio answer_count collection_length 100 %}%"></div>
</div>
<div class="mt-2 small">Started: {{ cid_user_exam.start_time }}</div>
@@ -20,12 +20,12 @@
{% endif %}
</div>
</div>
<div class="card border-secondary" style="min-width: 280px;">
<div class="card border-secondary summary-card">
<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 progress-compact mt-2">
<div class="progress-bar" role="progressbar" style="width: {% widthratio answer_count collection_length 100 %}%"></div>
</div>
{% else %}
@@ -6,7 +6,7 @@
<h2 class="h4 mb-1">{{ collection.name }}</h2>
<div class="text-muted small">Message hub for <strong>{{ target_label }}</strong></div>
</div>
<div class="card border-secondary" style="min-width: 320px;">
<div class="card border-secondary summary-card">
<div class="card-body py-2 px-3">
<div class="small text-muted">Outstanding messages</div>
<div class="fw-semibold {% if total_outstanding_messages %}text-danger{% endif %}">
+2 -2
View File
@@ -103,8 +103,8 @@
<small id="viewer-context" class="text-muted">No study/series selected</small>
</div>
</div>
<div class="card-body" style="position: relative; min-height: 450px;">
<div id="uploadviewer" class="dicom-viewer-root" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%;"></div>
<div class="card-body viewer-preupload-shell">
<div id="uploadviewer" class="dicom-viewer-root viewer-preupload-root"></div>
</div>
</div>
+3 -4
View File
@@ -30,8 +30,7 @@
<div class="card">
<div class="card-body p-2">
{% with image_url_array_and_count=series.get_image_url_array_and_count %}
<div id="root" class="dicom-viewer-root w-100" data-images="{{ image_url_array_and_count.0 }}"
style="height: 600px; box-sizing: border-box; background: #222;" data-auto-cache-stack=false>
<div id="root" class="dicom-viewer-root w-100 viewer-frame-standard" data-images="{{ image_url_array_and_count.0 }}" data-auto-cache-stack=false>
</div>
{% endwith %}
</div>
@@ -242,13 +241,13 @@
<div id="clone-findings-modal" class="modal modal-blur fade" tabindex="-1">
<div class="modal-dialog modal-dialog-centered related-finding-modal" style="max-width: 700px;">
<div class="modal-dialog modal-dialog-centered related-finding-modal modal-wide-md">
<div class="modal-content"></div>
</div>
</div>
<div id="series-tag-consistency-modal" class="modal modal-blur fade" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-xl" style="max-width: 1200px;">
<div class="modal-dialog modal-dialog-centered modal-xl modal-wide-xl">
<div class="modal-content"></div>
</div>
</div>
@@ -6,7 +6,7 @@
<h2 class="h4 mb-1">Messages Inbox</h2>
<div class="text-muted small">All accessible conversation threads across collections.</div>
</div>
<div class="card border-secondary" style="min-width: 320px;">
<div class="card border-secondary summary-card">
<div class="card-body py-2 px-3">
<div class="small text-muted">Pending acknowledgements</div>
<div class="fw-semibold {% if total_needs_your_ack %}text-danger{% endif %}">
@@ -7,7 +7,7 @@
<form method="post">
{% csrf_token %}
<textarea name="data" rows="18" style="width:100%">{% if request.POST.data %}{{ request.POST.data }}{% endif %}</textarea>
<textarea name="data" rows="18" class="w-100">{% if request.POST.data %}{{ request.POST.data }}{% endif %}</textarea>
<div style="margin-top:8px">
<button type="submit" name="action" value="preview" class="btn btn-primary">Preview</button>
<button type="submit" name="action" value="apply" class="btn btn-danger" onclick="return confirm('This will apply updates to matched trainee profiles. Are you sure?')">Apply updates</button>
@@ -40,9 +40,9 @@
<div>
<span style="color:red">No match</span>
<div>
<div class="d-inline-flex align-items-center" style="gap:6px">
<form hx-get="{% url 'generic:user_search' %}" hx-target="#user-results-{{ r.row_index }}" hx-swap="innerHTML" onsubmit="return false;" class="d-inline-flex align-items-center">
<input type="text" name="q" id="user-search-input-{{ r.row_index }}" placeholder="Search users..." style="width:220px" />
<div class="inline-search-row">
<form hx-get="{% url 'generic:user_search' %}" hx-target="#user-results-{{ r.row_index }}" hx-swap="innerHTML" onsubmit="return false;" class="inline-search-row">
<input type="text" name="q" id="user-search-input-{{ r.row_index }}" class="form-control form-control-sm search-input-compact" placeholder="Search users..." />
<input type="hidden" name="row" value="{{ r.row_index }}" />
<button type="submit" class="btn btn-sm btn-secondary">Search</button>
</form>
@@ -59,9 +59,9 @@
{% else %}
<div>
<span style="color:red">No match</span>
<div class="d-inline-flex align-items-center" style="gap:6px">
<form hx-get="{% url 'generic:supervisor_search' %}" hx-target="#supervisor-results-{{ r.row_index }}" hx-swap="innerHTML" onsubmit="return false;" class="d-inline-flex align-items-center">
<input type="text" name="q" id="supervisor-search-input-{{ r.row_index }}" placeholder="Search supervisors..." style="width:220px" />
<div class="inline-search-row">
<form hx-get="{% url 'generic:supervisor_search' %}" hx-target="#supervisor-results-{{ r.row_index }}" hx-swap="innerHTML" onsubmit="return false;" class="inline-search-row">
<input type="text" name="q" id="supervisor-search-input-{{ r.row_index }}" class="form-control form-control-sm search-input-compact" placeholder="Search supervisors..." />
<input type="hidden" name="row" value="{{ r.row_index }}" />
<button type="submit" class="btn btn-sm btn-secondary">Search</button>
</form>
+98
View File
@@ -0,0 +1,98 @@
/* Shared responsive helpers for layout, forms, cards, and viewer surfaces. */
.summary-card {
width: 100%;
max-width: 22rem;
}
@media (min-width: 576px) {
.summary-card {
width: auto;
min-width: 17.5rem;
}
}
.form-control-max-300 {
width: 100%;
max-width: 300px;
}
.form-control-max-320 {
width: 100%;
max-width: 320px;
}
.search-input-compact {
width: min(100%, 220px);
}
.inline-search-row {
display: flex;
align-items: center;
gap: 0.375rem;
flex-wrap: wrap;
}
.viewer-frame-setup {
box-sizing: border-box;
background: #222;
width: 100%;
max-width: 1600px;
height: min(72vh, 800px);
min-height: 420px;
}
.viewer-frame-standard {
box-sizing: border-box;
background: #222;
width: 100%;
height: min(68vh, 600px);
min-height: 360px;
}
.viewer-frame-ohif {
width: 100%;
height: 100%;
border: none;
padding: 0;
min-height: min(70vh, 700px);
}
.viewer-preupload-shell {
position: relative;
min-height: clamp(320px, 55vh, 450px);
}
.viewer-preupload-root {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.modal-wide-md {
max-width: min(700px, 96vw);
}
.modal-wide-xl {
max-width: min(1200px, 96vw);
}
.progress-compact {
height: 8px;
}
.progress-compact-sm {
height: 10px;
}
.flex-max-320 {
flex: 1;
max-width: 320px;
}
@media (max-width: 575.98px) {
.flex-max-320 {
max-width: 100%;
}
}
+98
View File
@@ -0,0 +1,98 @@
/* Shared responsive helpers for layout, forms, cards, and viewer surfaces. */
.summary-card {
width: 100%;
max-width: 22rem;
}
@media (min-width: 576px) {
.summary-card {
width: auto;
min-width: 17.5rem;
}
}
.form-control-max-300 {
width: 100%;
max-width: 300px;
}
.form-control-max-320 {
width: 100%;
max-width: 320px;
}
.search-input-compact {
width: min(100%, 220px);
}
.inline-search-row {
display: flex;
align-items: center;
gap: 0.375rem;
flex-wrap: wrap;
}
.viewer-frame-setup {
box-sizing: border-box;
background: #222;
width: 100%;
max-width: 1600px;
height: min(72vh, 800px);
min-height: 420px;
}
.viewer-frame-standard {
box-sizing: border-box;
background: #222;
width: 100%;
height: min(68vh, 600px);
min-height: 360px;
}
.viewer-frame-ohif {
width: 100%;
height: 100%;
border: none;
padding: 0;
min-height: min(70vh, 700px);
}
.viewer-preupload-shell {
position: relative;
min-height: clamp(320px, 55vh, 450px);
}
.viewer-preupload-root {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.modal-wide-md {
max-width: min(700px, 96vw);
}
.modal-wide-xl {
max-width: min(1200px, 96vw);
}
.progress-compact {
height: 8px;
}
.progress-compact-sm {
height: 10px;
}
.flex-max-320 {
flex: 1;
max-width: 320px;
}
@media (max-width: 575.98px) {
.flex-max-320 {
max-width: 100%;
}
}
+3 -3
View File
@@ -38,13 +38,13 @@
<p>Please note users created this way bypass some of the validations so check details such as email addresses are correct</p>
<div class="mb-2">
<label for="input-format" class="form-label">Input format</label>
<select id="input-format" class="form-select" style="width:300px">
<select id="input-format" class="form-select form-control-max-300">
<option value="spreadsheet">First,Last,Email,Grade,Supervisor,SupervisorEmail (spreadsheet)</option>
<option value="name-supervisor-email-grade">User \t Educational Supervisor \t Email \t Grade</option>
</select>
</div>
<textarea id="csv" placeholder="Paste users content here" style="width: 300px; height: 100px;"></textarea><br/>
<textarea id="csv" class="form-control form-control-max-300" rows="4" placeholder="Paste users content here"></textarea><br/>
<input type="button" value="Load Data" _="on click createTable()" >
@@ -159,7 +159,7 @@
console.log(user)
$("#users-list").append(`<li data-index="${i}">Name: <span class="name">${user.first_name} ${user.last_name}</span><br/>Email: <span class="email">${user.email}</span><br/>Grade: <span class="grade">${user.grade}</span><br/>Supervisor: <span class="supervisor_name">${user.supervisor_name || ''}</span><br/>
<form>
Supervisor email: <input class="supervisor-email-input" name="supervisor_email" type="text" value="${user.supervisor_email || ''}" placeholder="supervisor email" style="width: 320px; display: none;" /> <br/>
Supervisor email: <input class="supervisor-email-input form-control form-control-sm form-control-max-320" name="supervisor_email" type="text" value="${user.supervisor_email || ''}" placeholder="supervisor email" style="display: none;" /> <br/>
<input type="text" name="first_name" value="${user.first_name}" hidden>
<input type="text" name="last_name" value="${user.last_name}" hidden>
<input type="text" name="email" value="${user.email}" hidden>
+1
View File
@@ -79,6 +79,7 @@
{% endblock %}
<link rel="stylesheet" href="{% static 'css/anatomy.css' %}">
<link rel="stylesheet" href="{% static 'css/forms.css' %}">
<link rel="stylesheet" href="{% static 'css/responsive-utils.css' %}">
<style>
:root {