634 lines
30 KiB
HTML
634 lines
30 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Imaging Series - {{ study.study_description|default:"Study" }} - Hospital Management{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="content">
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h4>Imaging Series</h4>
|
|
<h6>Study: {{ study.study_description|default:"Imaging Study" }}</h6>
|
|
</div>
|
|
<div class="page-btn">
|
|
<a href="{% url 'radiology:imaging_study_detail' study.pk %}" class="btn btn-secondary me-2">
|
|
<i class="fas fa-arrow-left me-1"></i>Back to Study
|
|
</a>
|
|
<a href="{% url 'radiology:imaging_series_create' study.pk %}" class="btn btn-added">
|
|
<i class="fas fa-plus me-1"></i>Add Series
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Study Information -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-info-circle me-2"></i>Study Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="info-group">
|
|
<label class="form-label">Patient:</label>
|
|
<p class="fw-bold">{{ study.patient.get_full_name }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="info-group">
|
|
<label class="form-label">Accession Number:</label>
|
|
<p>{{ study.accession_number }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="info-group">
|
|
<label class="form-label">Study Date:</label>
|
|
<p>{{ study.study_date|date:"M d, Y" }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="info-group">
|
|
<label class="form-label">Modality:</label>
|
|
<p><span class="badge bg-primary">{{ study.get_modality_display }}</span></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Series Statistics -->
|
|
<div class="row mb-4">
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-layer-group"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="total-series">{{ series.count }}</h5>
|
|
<h6>Total Series</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-images"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="total-images">{{ total_images|default:0 }}</h5>
|
|
<h6>Total Images</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-check-circle"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="completed-series">{{ completed_series|default:0 }}</h5>
|
|
<h6>Completed Series</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-clock"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="processing-series">{{ processing_series|default:0 }}</h5>
|
|
<h6>Processing</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters and Search -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="row align-items-end">
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Search Series:</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="search-input"
|
|
placeholder="Search by description, protocol...">
|
|
<button class="btn btn-outline-secondary" type="button" id="search-btn">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<label class="form-label">Modality:</label>
|
|
<select class="form-select" id="modality-filter">
|
|
<option value="">All Modalities</option>
|
|
<option value="CT">CT</option>
|
|
<option value="MR">MR</option>
|
|
<option value="US">US</option>
|
|
<option value="DX">DX</option>
|
|
<option value="CR">CR</option>
|
|
<option value="XA">XA</option>
|
|
<option value="RF">RF</option>
|
|
<option value="MG">MG</option>
|
|
<option value="NM">NM</option>
|
|
<option value="PT">PT</option>
|
|
<option value="OT">Other</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<label class="form-label">Status:</label>
|
|
<select class="form-select" id="status-filter">
|
|
<option value="">All Status</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="processing">Processing</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="failed">Failed</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label">Date Range:</label>
|
|
<input type="date" class="form-control" id="date-filter">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-group">
|
|
<button class="btn btn-outline-secondary me-2" onclick="clearFilters()">
|
|
<i class="fas fa-times me-1"></i>Clear
|
|
</button>
|
|
<button class="btn btn-primary" onclick="applyFilters()">
|
|
<i class="fas fa-filter me-1"></i>Filter
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Series List -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-layer-group me-2"></i>Imaging Series
|
|
</h5>
|
|
<div class="card-tools">
|
|
<div class="btn-group">
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="toggleView('grid')" id="grid-view-btn">
|
|
<i class="fas fa-th"></i>
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm active" onclick="toggleView('list')" id="list-view-btn">
|
|
<i class="fas fa-list"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Grid View -->
|
|
<div id="grid-view" class="d-none">
|
|
<div class="row" id="series-grid">
|
|
{% for series in series %}
|
|
<div class="col-lg-4 col-md-6 col-12 mb-4 series-card"
|
|
data-modality="{{ series.modality }}"
|
|
data-status="{{ series.status|default:'completed' }}"
|
|
data-date="{{ series.series_date|date:'Y-m-d' }}">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h6 class="card-title mb-0">
|
|
Series {{ series.series_number }}
|
|
</h6>
|
|
<span class="badge bg-{{ series.get_status_color|default:'success' }}">
|
|
{{ series.get_status_display|default:'Completed' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="series-info">
|
|
<div class="info-row">
|
|
<span class="label">Description:</span>
|
|
<span class="value">{{ series.series_description|default:"No description" }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Modality:</span>
|
|
<span class="value">
|
|
<span class="badge bg-primary">{{ series.get_modality_display }}</span>
|
|
</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Protocol:</span>
|
|
<span class="value">{{ series.protocol_name|default:"N/A" }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Images:</span>
|
|
<span class="value">{{ series.number_of_images|default:0 }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="label">Date/Time:</span>
|
|
<span class="value">{{ series.series_datetime|date:"M d, Y H:i" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="btn-group w-100">
|
|
<a href="{% url 'radiology:imaging_series_detail' series.pk %}"
|
|
class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-eye me-1"></i>View
|
|
</a>
|
|
<a href="{% url 'radiology:imaging_series_edit' series.pk %}"
|
|
class="btn btn-outline-secondary btn-sm">
|
|
<i class="fas fa-edit me-1"></i>Edit
|
|
</a>
|
|
<button class="btn btn-outline-info btn-sm"
|
|
onclick="viewImages('{{ series.pk }}')">
|
|
<i class="fas fa-images me-1"></i>Images
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-layer-group fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No Series Found</h5>
|
|
<p class="text-muted">No imaging series found for this study.</p>
|
|
<a href="{% url 'radiology:imaging_series_create' study.pk %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Add First Series
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List View -->
|
|
<div id="list-view">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" id="series-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Series #</th>
|
|
<th>Description</th>
|
|
<th>Modality</th>
|
|
<th>Protocol</th>
|
|
<th>Images</th>
|
|
<th>Date/Time</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for series in series %}
|
|
<tr class="series-row"
|
|
data-modality="{{ series.modality }}"
|
|
data-status="{{ series.status|default:'completed' }}"
|
|
data-date="{{ series.series_date|date:'Y-m-d' }}">
|
|
<td>
|
|
<span class="fw-bold">{{ series.series_number }}</span>
|
|
</td>
|
|
<td>
|
|
<div class="series-desc">
|
|
<div class="fw-bold">{{ series.series_description|default:"No description" }}</div>
|
|
<small class="text-muted">{{ series.series_instance_uid|truncatechars:30 }}</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-primary">{{ series.get_modality_display }}</span>
|
|
</td>
|
|
<td>{{ series.protocol_name|default:"N/A" }}</td>
|
|
<td>
|
|
<span class="fw-bold">{{ series.number_of_images|default:0 }}</span>
|
|
</td>
|
|
<td>
|
|
<div>{{ series.series_date|date:"M d, Y" }}</div>
|
|
<small class="text-muted">{{ series.series_time|time:"H:i" }}</small>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{{ series.get_status_color|default:'success' }}">
|
|
{{ series.get_status_display|default:'Completed' }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="{% url 'radiology:imaging_series_detail' series.pk %}"
|
|
class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'radiology:imaging_series_edit' series.pk %}"
|
|
class="btn btn-outline-secondary btn-sm" title="Edit Series">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<button class="btn btn-outline-info btn-sm"
|
|
onclick="viewImages('{{ series.pk }}')" title="View Images">
|
|
<i class="fas fa-images"></i>
|
|
</button>
|
|
<div class="btn-group">
|
|
<button class="btn btn-outline-secondary btn-sm dropdown-toggle"
|
|
data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'radiology:imaging_series_download' series.pk %}">
|
|
<i class="fas fa-download me-2"></i>Download DICOM
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a class="dropdown-item" href="#" onclick="exportSeries('{{ series.pk }}')">
|
|
<i class="fas fa-file-export me-2"></i>Export
|
|
</a>
|
|
</li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li>
|
|
<a class="dropdown-item text-danger" href="{% url 'radiology:imaging_series_delete' series.pk %}">
|
|
<i class="fas fa-trash me-2"></i>Delete
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="8" class="text-center py-5">
|
|
<i class="fas fa-layer-group fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No Series Found</h5>
|
|
<p class="text-muted">No imaging series found for this study.</p>
|
|
<a href="{% url 'radiology:imaging_series_create' study.pk %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Add First Series
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="d-flex justify-content-between align-items-center mt-4">
|
|
<div class="pagination-info">
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} series
|
|
</div>
|
|
<nav>
|
|
<ul class="pagination mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1">« First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last »</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize view
|
|
toggleView('list');
|
|
|
|
// Search functionality
|
|
$('#search-btn').on('click', function() {
|
|
applyFilters();
|
|
});
|
|
|
|
$('#search-input').on('keypress', function(e) {
|
|
if (e.which === 13) {
|
|
applyFilters();
|
|
}
|
|
});
|
|
});
|
|
|
|
function toggleView(viewType) {
|
|
if (viewType === 'grid') {
|
|
$('#list-view').addClass('d-none');
|
|
$('#grid-view').removeClass('d-none');
|
|
$('#list-view-btn').removeClass('active');
|
|
$('#grid-view-btn').addClass('active');
|
|
} else {
|
|
$('#grid-view').addClass('d-none');
|
|
$('#list-view').removeClass('d-none');
|
|
$('#grid-view-btn').removeClass('active');
|
|
$('#list-view-btn').addClass('active');
|
|
}
|
|
}
|
|
|
|
function applyFilters() {
|
|
const searchTerm = $('#search-input').val().toLowerCase();
|
|
const modalityFilter = $('#modality-filter').val();
|
|
const statusFilter = $('#status-filter').val();
|
|
const dateFilter = $('#date-filter').val();
|
|
|
|
// Filter grid view
|
|
$('.series-card').each(function() {
|
|
const card = $(this);
|
|
const description = card.find('.series-info').text().toLowerCase();
|
|
const modality = card.data('modality');
|
|
const status = card.data('status');
|
|
const date = card.data('date');
|
|
|
|
let show = true;
|
|
|
|
if (searchTerm && !description.includes(searchTerm)) {
|
|
show = false;
|
|
}
|
|
|
|
if (modalityFilter && modality !== modalityFilter) {
|
|
show = false;
|
|
}
|
|
|
|
if (statusFilter && status !== statusFilter) {
|
|
show = false;
|
|
}
|
|
|
|
if (dateFilter && date !== dateFilter) {
|
|
show = false;
|
|
}
|
|
|
|
card.toggle(show);
|
|
});
|
|
|
|
// Filter list view
|
|
$('.series-row').each(function() {
|
|
const row = $(this);
|
|
const description = row.find('.series-desc').text().toLowerCase();
|
|
const modality = row.data('modality');
|
|
const status = row.data('status');
|
|
const date = row.data('date');
|
|
|
|
let show = true;
|
|
|
|
if (searchTerm && !description.includes(searchTerm)) {
|
|
show = false;
|
|
}
|
|
|
|
if (modalityFilter && modality !== modalityFilter) {
|
|
show = false;
|
|
}
|
|
|
|
if (statusFilter && status !== statusFilter) {
|
|
show = false;
|
|
}
|
|
|
|
if (dateFilter && date !== dateFilter) {
|
|
show = false;
|
|
}
|
|
|
|
row.toggle(show);
|
|
});
|
|
}
|
|
|
|
function clearFilters() {
|
|
$('#search-input').val('');
|
|
$('#modality-filter').val('');
|
|
$('#status-filter').val('');
|
|
$('#date-filter').val('');
|
|
|
|
$('.series-card, .series-row').show();
|
|
}
|
|
|
|
function viewImages(seriesId) {
|
|
// Open image viewer for series
|
|
window.open(`/radiology/series/${seriesId}/images/`, '_blank');
|
|
}
|
|
|
|
function exportSeries(seriesId) {
|
|
if (confirm('Export this imaging series?')) {
|
|
window.location.href = `/radiology/series/${seriesId}/export/`;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.info-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.info-group .form-label {
|
|
font-weight: 600;
|
|
color: #495057;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.info-group p {
|
|
margin-bottom: 0;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.series-info .info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
padding: 4px 0;
|
|
border-bottom: 1px solid #f8f9fa;
|
|
}
|
|
|
|
.series-info .info-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.series-info .label {
|
|
font-weight: 600;
|
|
color: #495057;
|
|
flex: 0 0 40%;
|
|
}
|
|
|
|
.series-info .value {
|
|
color: #6c757d;
|
|
text-align: right;
|
|
}
|
|
|
|
.series-desc {
|
|
max-width: 200px;
|
|
}
|
|
|
|
.card-tools {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.btn-group .btn.active {
|
|
background-color: #007bff;
|
|
color: white;
|
|
border-color: #007bff;
|
|
}
|
|
|
|
/* Mobile Responsive */
|
|
@media (max-width: 768px) {
|
|
.series-info .info-row {
|
|
flex-direction: column;
|
|
text-align: left;
|
|
}
|
|
|
|
.series-info .value {
|
|
text-align: left;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.btn-group {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
margin-bottom: 2px;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|