Marwan Alwali 0a037d3d9d update
2025-09-01 11:26:11 +03:00

1145 lines
40 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}DICOM Files Management{% endblock %}
{% block css %}
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-buttons-bs5/css/buttons.bootstrap5.min.css' %}" rel="stylesheet" />
<style>
.page-header-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
overflow: hidden;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: var(--card-color);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
color: white;
font-size: 1.25rem;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #495057;
margin-bottom: 0.5rem;
}
.stat-label {
color: #6c757d;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
}
.filters-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.filter-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
align-items: end;
}
.files-table-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
overflow: hidden;
}
.section-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
display: flex;
justify-content: between;
align-items: center;
}
.file-thumbnail {
width: 60px;
height: 60px;
border-radius: 0.375rem;
background: #f8f9fa;
border: 1px solid #dee2e6;
display: flex;
align-items: center;
justify-content: center;
color: #6c757d;
font-size: 1.5rem;
cursor: pointer;
transition: all 0.2s;
}
.file-thumbnail:hover {
background: #e3f2fd;
color: #1976d2;
transform: scale(1.05);
}
.file-info {
flex: 1;
}
.file-name {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.file-details {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 0.25rem;
}
.file-meta {
font-size: 0.75rem;
color: #adb5bd;
}
.patient-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.patient-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
}
.modality-badge {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.modality-ct { background: #e3f2fd; color: #1976d2; }
.modality-mri { background: #f3e5f5; color: #7b1fa2; }
.modality-xr { background: #e8f5e8; color: #388e3c; }
.modality-us { background: #fff3e0; color: #f57c00; }
.modality-cr { background: #fce4ec; color: #c2185b; }
.modality-dx { background: #e0f2f1; color: #00796b; }
.status-badge {
padding: 0.375rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-uploaded { background: #e8f5e8; color: #2e7d32; }
.status-processing { background: #fff3cd; color: #856404; }
.status-processed { background: #d1ecf1; color: #0c5460; }
.status-archived { background: #f8f9fa; color: #6c757d; }
.status-error { background: #f8d7da; color: #721c24; }
.action-buttons {
display: flex;
gap: 0.25rem;
}
.btn-action {
padding: 0.375rem 0.5rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
}
.btn-view { background: #e3f2fd; color: #1976d2; }
.btn-download { background: #e8f5e8; color: #2e7d32; }
.btn-edit { background: #fff3e0; color: #f57c00; }
.btn-delete { background: #ffebee; color: #d32f2f; }
.btn-analyze { background: #f3e5f5; color: #7b1fa2; }
.btn-action:hover {
transform: scale(1.05);
opacity: 0.8;
}
.bulk-actions {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
display: none;
}
.bulk-actions.show {
display: block;
}
.quick-filters {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.quick-filter {
padding: 0.5rem 1rem;
border: 1px solid #dee2e6;
background: white;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
text-decoration: none;
color: #495057;
}
.quick-filter:hover, .quick-filter.active {
background: #007bff;
color: white;
border-color: #007bff;
text-decoration: none;
}
.file-size {
font-weight: bold;
color: #6c757d;
}
.upload-progress {
background: #e9ecef;
border-radius: 0.25rem;
height: 4px;
overflow: hidden;
margin-top: 0.5rem;
}
.progress-bar {
background: #007bff;
height: 100%;
transition: width 0.3s ease;
}
.dicom-tags {
font-size: 0.75rem;
color: #6c757d;
margin-top: 0.25rem;
}
.study-info {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 0.5rem;
margin-top: 0.5rem;
}
.study-date {
font-weight: 600;
color: #495057;
}
.study-description {
font-size: 0.875rem;
color: #6c757d;
}
@media (max-width: 768px) {
.page-header-section {
padding: 1.5rem;
}
.stats-cards {
grid-template-columns: repeat(2, 1fr);
}
.filter-row {
grid-template-columns: 1fr;
}
.quick-filters {
justify-content: center;
}
.action-buttons {
flex-direction: column;
}
.file-thumbnail {
width: 40px;
height: 40px;
font-size: 1rem;
}
}
@media print {
.filters-section, .bulk-actions, .action-buttons {
display: none !important;
}
.section-header {
background: none;
border-bottom: 2px solid #000;
color: #000;
}
}
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<!-- Page Header -->
<div class="d-flex align-items-center mb-3">
<div>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'radiology:dashboard' %}">Radiology</a></li>
<li class="breadcrumb-item active">DICOM Files</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>DICOM Files Management
</h1>
</div>
<div class="ms-auto">
<button type="button" class="btn btn-outline-secondary me-2" onclick="exportFiles()">
<i class="fas fa-download me-1"></i>Export
</button>
<button type="button" class="btn btn-outline-info me-2" onclick="bulkUpload()">
<i class="fas fa-upload me-1"></i>Bulk Upload
</button>
{# <a href="{% url 'radiology:dicom_upload' %}" class="btn btn-primary">#}
{# <i class="fas fa-plus me-1"></i>Upload DICOM#}
{# </a>#}
</div>
</div>
<!-- Statistics Cards -->
<div class="stats-cards">
<div class="stat-card" style="--card-color: #007bff;">
<div class="stat-icon" style="background: #007bff;">
<i class="fas fa-file-medical"></i>
</div>
<div class="stat-number">{{ stats.total_files|default:0 }}</div>
<div class="stat-label">Total Files</div>
</div>
<div class="stat-card" style="--card-color: #28a745;">
<div class="stat-icon" style="background: #28a745;">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-number">{{ stats.processed_files|default:0 }}</div>
<div class="stat-label">Processed</div>
</div>
<div class="stat-card" style="--card-color: #ffc107;">
<div class="stat-icon" style="background: #ffc107;">
<i class="fas fa-clock"></i>
</div>
<div class="stat-number">{{ stats.pending_files|default:0 }}</div>
<div class="stat-label">Processing</div>
</div>
<div class="stat-card" style="--card-color: #17a2b8;">
<div class="stat-icon" style="background: #17a2b8;">
<i class="fas fa-hdd"></i>
</div>
<div class="stat-number">{{ stats.total_size|default:"0 GB" }}</div>
<div class="stat-label">Storage Used</div>
</div>
<div class="stat-card" style="--card-color: #6f42c1;">
<div class="stat-icon" style="background: #6f42c1;">
<i class="fas fa-users"></i>
</div>
<div class="stat-number">{{ stats.unique_patients|default:0 }}</div>
<div class="stat-label">Patients</div>
</div>
</div>
<!-- Quick Filters -->
<div class="quick-filters">
<a href="?status=all" class="quick-filter {% if not request.GET.status or request.GET.status == 'all' %}active{% endif %}">
<i class="fas fa-list me-1"></i>All Files
</a>
<a href="?status=uploaded" class="quick-filter {% if request.GET.status == 'uploaded' %}active{% endif %}">
<i class="fas fa-upload me-1"></i>Uploaded
</a>
<a href="?status=processing" class="quick-filter {% if request.GET.status == 'processing' %}active{% endif %}">
<i class="fas fa-clock me-1"></i>Processing
</a>
<a href="?status=processed" class="quick-filter {% if request.GET.status == 'processed' %}active{% endif %}">
<i class="fas fa-check me-1"></i>Processed
</a>
<a href="?modality=CT" class="quick-filter {% if request.GET.modality == 'CT' %}active{% endif %}">
<i class="fas fa-x-ray me-1"></i>CT Scans
</a>
<a href="?modality=MRI" class="quick-filter {% if request.GET.modality == 'MRI' %}active{% endif %}">
<i class="fas fa-brain me-1"></i>MRI
</a>
<a href="?modality=XR" class="quick-filter {% if request.GET.modality == 'XR' %}active{% endif %}">
<i class="fas fa-bone me-1"></i>X-Ray
</a>
</div>
<!-- Filters Section -->
<div class="filters-section">
<h6 class="mb-3">
<i class="fas fa-filter me-2"></i>Advanced Filters
</h6>
<form method="get" id="filter-form">
<div class="filter-row">
<div>
<label class="form-label">Patient Name/ID</label>
<input type="text" class="form-control" name="patient"
value="{{ request.GET.patient }}" placeholder="Search patient...">
</div>
<div>
<label class="form-label">Study Date</label>
<input type="date" class="form-control" name="study_date"
value="{{ request.GET.study_date }}">
</div>
<div>
<label class="form-label">Modality</label>
<select class="form-select" name="modality">
<option value="">All Modalities</option>
<option value="CT" {% if request.GET.modality == 'CT' %}selected{% endif %}>CT</option>
<option value="MRI" {% if request.GET.modality == 'MRI' %}selected{% endif %}>MRI</option>
<option value="XR" {% if request.GET.modality == 'XR' %}selected{% endif %}>X-Ray</option>
<option value="US" {% if request.GET.modality == 'US' %}selected{% endif %}>Ultrasound</option>
<option value="CR" {% if request.GET.modality == 'CR' %}selected{% endif %}>CR</option>
<option value="DX" {% if request.GET.modality == 'DX' %}selected{% endif %}>DX</option>
</select>
</div>
<div>
<label class="form-label">Status</label>
<select class="form-select" name="status">
<option value="">All Statuses</option>
<option value="uploaded" {% if request.GET.status == 'uploaded' %}selected{% endif %}>Uploaded</option>
<option value="processing" {% if request.GET.status == 'processing' %}selected{% endif %}>Processing</option>
<option value="processed" {% if request.GET.status == 'processed' %}selected{% endif %}>Processed</option>
<option value="archived" {% if request.GET.status == 'archived' %}selected{% endif %}>Archived</option>
<option value="error" {% if request.GET.status == 'error' %}selected{% endif %}>Error</option>
</select>
</div>
<div>
<label class="form-label">File Size</label>
<select class="form-select" name="size_range">
<option value="">Any Size</option>
<option value="small" {% if request.GET.size_range == 'small' %}selected{% endif %}>< 10 MB</option>
<option value="medium" {% if request.GET.size_range == 'medium' %}selected{% endif %}>10-100 MB</option>
<option value="large" {% if request.GET.size_range == 'large' %}selected{% endif %}>100-500 MB</option>
<option value="xlarge" {% if request.GET.size_range == 'xlarge' %}selected{% endif %}>> 500 MB</option>
</select>
</div>
<div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-1"></i>Filter
</button>
{# <a href="{% url 'radiology:dicom_file_list' %}" class="btn btn-outline-secondary ms-2">#}
{# <i class="fas fa-times me-1"></i>Clear#}
{# </a>#}
</div>
</div>
</form>
</div>
<!-- Bulk Actions -->
<div class="bulk-actions" id="bulk-actions">
<div class="d-flex align-items-center justify-content-between">
<div>
<span id="selected-count">0</span> files selected
</div>
<div>
<button type="button" class="btn btn-outline-primary btn-sm me-2" onclick="bulkDownload()">
<i class="fas fa-download me-1"></i>Download Selected
</button>
<button type="button" class="btn btn-outline-info btn-sm me-2" onclick="bulkAnalyze()">
<i class="fas fa-search me-1"></i>Analyze Selected
</button>
<button type="button" class="btn btn-outline-warning btn-sm me-2" onclick="bulkArchive()">
<i class="fas fa-archive me-1"></i>Archive Selected
</button>
<button type="button" class="btn btn-outline-danger btn-sm" onclick="bulkDelete()">
<i class="fas fa-trash me-1"></i>Delete Selected
</button>
</div>
</div>
</div>
<!-- Files Table -->
<div class="files-table-section">
<div class="section-header">
<div>
<i class="fas fa-table me-2"></i>DICOM Files ({{ files|length }})
</div>
<div class="d-flex align-items-center gap-2">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="auto-refresh">
<label class="form-check-label" for="auto-refresh">Auto Refresh</label>
</div>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="refreshTable()">
<i class="fas fa-sync"></i>
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0" id="files-table">
<thead class="table-light">
<tr>
<th width="40">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="select-all">
</div>
</th>
<th>File</th>
<th>Patient</th>
<th>Study Info</th>
<th>Modality</th>
<th>Size</th>
<th>Upload Date</th>
<th>Status</th>
<th width="150">Actions</th>
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<td>
<div class="form-check">
<input class="form-check-input file-checkbox" type="checkbox" value="{{ file.id }}">
</div>
</td>
<td>
<div class="d-flex align-items-center gap-3">
<div class="file-thumbnail" onclick="viewFile({{ file.id }})">
{% if file.thumbnail %}
<img src="{{ file.thumbnail.url }}" alt="Thumbnail" style="width: 100%; height: 100%; object-fit: cover; border-radius: 0.375rem;">
{% else %}
<i class="fas fa-file-medical"></i>
{% endif %}
</div>
<div class="file-info">
<div class="file-name">{{ file.filename }}</div>
<div class="file-details">{{ file.series_description|default:"No description" }}</div>
<div class="file-meta">
Instance: {{ file.instance_number|default:"N/A" }} |
Slice: {{ file.slice_location|default:"N/A" }}
</div>
{% if file.dicom_tags %}
<div class="dicom-tags">
SOP: {{ file.dicom_tags.SOPInstanceUID|truncatechars:20 }}
</div>
{% endif %}
</div>
</div>
</td>
<td>
<div class="patient-info">
<div class="patient-avatar">
{{ file.patient_name.0|upper|default:"P" }}
</div>
<div>
<div class="fw-bold">{{ file.patient_name|default:"Unknown Patient" }}</div>
<small class="text-muted">ID: {{ file.patient_id|default:"N/A" }}</small>
</div>
</div>
</td>
<td>
<div class="study-info">
<div class="study-date">{{ file.study_date|date:"M d, Y"|default:"N/A" }}</div>
<div class="study-description">{{ file.study_description|truncatechars:30|default:"No description" }}</div>
{% if file.study_time %}
<small class="text-muted">{{ file.study_time|time:"g:i A" }}</small>
{% endif %}
</div>
</td>
<td>
<span class="modality-badge modality-{{ file.modality|lower }}">
{{ file.modality|default:"Unknown" }}
</span>
</td>
<td>
<div class="file-size">{{ file.file_size|filesizeformat }}</div>
{% if file.compression_ratio %}
<small class="text-muted">{{ file.compression_ratio }}% compressed</small>
{% endif %}
</td>
<td>
<div class="fw-bold">{{ file.uploaded_at|date:"M d, Y" }}</div>
<small class="text-muted">{{ file.uploaded_at|time:"g:i A" }}</small>
</td>
<td>
<span class="status-badge status-{{ file.status }}">
{{ file.get_status_display }}
</span>
{% if file.status == 'processing' %}
<div class="upload-progress">
<div class="progress-bar" style="width: {{ file.processing_progress|default:0 }}%"></div>
</div>
{% endif %}
</td>
<td>
<div class="action-buttons">
<button type="button" class="btn-action btn-view"
onclick="viewFile({{ file.id }})" title="View DICOM">
<i class="fas fa-eye"></i>
</button>
<button type="button" class="btn-action btn-download"
onclick="downloadFile({{ file.id }})" title="Download">
<i class="fas fa-download"></i>
</button>
<button type="button" class="btn-action btn-analyze"
onclick="analyzeFile({{ file.id }})" title="Analyze">
<i class="fas fa-search"></i>
</button>
{% if file.can_edit %}
<button type="button" class="btn-action btn-edit"
onclick="editFile({{ file.id }})" title="Edit Metadata">
<i class="fas fa-edit"></i>
</button>
{% endif %}
{% if file.can_delete %}
<button type="button" class="btn-action btn-delete"
onclick="deleteFile({{ file.id }})" title="Delete">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center py-4">
<div class="text-muted">
<i class="fas fa-file-medical fa-3x mb-3"></i>
<h5>No DICOM Files Found</h5>
<p>No DICOM files match your current filters.</p>
{# <a href="{% url 'radiology:dicom_upload' %}" class="btn btn-primary">#}
{# <i class="fas fa-plus me-1"></i>Upload First File#}
{# </a>#}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="d-flex justify-content-between align-items-center p-3">
<div class="text-muted">
Showing {{ files|length }} of {{ total_files }} files
</div>
<nav aria-label="Files pagination">
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{{ request.GET.urlencode }}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{{ request.GET.urlencode }}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{{ request.GET.urlencode }}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{{ request.GET.urlencode }}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
</div>
<!-- File Details Modal -->
<div class="modal fade" id="fileDetailsModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-file-medical me-2"></i>DICOM File Details
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="file-details-content">
<!-- File details will be loaded here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Close
</button>
<button type="button" class="btn btn-primary" onclick="openInViewer()">
<i class="fas fa-eye me-1"></i>Open in Viewer
</button>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-exclamation-triangle me-2 text-danger"></i>Delete DICOM File
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
Are you sure you want to delete this DICOM file? This action cannot be undone.
</div>
<div class="mb-3">
<label class="form-label">Reason for Deletion</label>
<textarea class="form-control" id="deletion-reason" rows="3"
placeholder="Please provide a reason for deleting this file..." required></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-danger" onclick="confirmDeletion()">
<i class="fas fa-trash me-1"></i>Delete File
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-buttons-bs5/js/buttons.bootstrap5.min.js' %}"></script>
<script>
let currentFileId = null;
$(document).ready(function() {
// Initialize DataTable
$('#files-table').DataTable({
responsive: true,
pageLength: 25,
order: [[6, 'desc']], // Sort by upload date
columnDefs: [
{ orderable: false, targets: [0, 8] } // Disable sorting for checkbox and actions
]
});
// Handle select all checkbox
$('#select-all').change(function() {
$('.file-checkbox').prop('checked', this.checked);
updateBulkActions();
});
// Handle individual checkboxes
$('.file-checkbox').change(function() {
updateBulkActions();
// Update select all checkbox
const totalCheckboxes = $('.file-checkbox').length;
const checkedCheckboxes = $('.file-checkbox:checked').length;
$('#select-all').prop('checked', totalCheckboxes === checkedCheckboxes);
});
// Auto-refresh functionality
let autoRefreshInterval;
$('#auto-refresh').change(function() {
if (this.checked) {
autoRefreshInterval = setInterval(refreshTable, 30000); // Refresh every 30 seconds
} else {
clearInterval(autoRefreshInterval);
}
});
});
function updateBulkActions() {
const selectedCount = $('.file-checkbox:checked').length;
$('#selected-count').text(selectedCount);
if (selectedCount > 0) {
$('#bulk-actions').addClass('show');
} else {
$('#bulk-actions').removeClass('show');
}
}
function refreshTable() {
location.reload();
}
function exportFiles() {
const selectedFiles = $('.file-checkbox:checked').map(function() {
return this.value;
}).get();
let url = '/radiology/dicom/export/';
if (selectedFiles.length > 0) {
url += '?files=' + selectedFiles.join(',');
}
window.open(url, '_blank');
}
function bulkUpload() {
window.location.href = '/radiology/dicom/bulk-upload/';
}
function viewFile(fileId) {
currentFileId = fileId;
fetch(`/radiology/dicom/${fileId}/details/`, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
displayFileDetails(data.file);
new bootstrap.Modal(document.getElementById('fileDetailsModal')).show();
} else {
showAlert('Error loading file details', 'danger');
}
})
.catch(error => {
showAlert('Error loading file details', 'danger');
});
}
function displayFileDetails(file) {
const content = `
<div class="row">
<div class="col-md-6">
<h6>File Information</h6>
<table class="table table-sm">
<tr><td><strong>Filename:</strong></td><td>${file.filename}</td></tr>
<tr><td><strong>Size:</strong></td><td>${file.file_size}</td></tr>
<tr><td><strong>Upload Date:</strong></td><td>${file.uploaded_at}</td></tr>
<tr><td><strong>Status:</strong></td><td>${file.status}</td></tr>
</table>
<h6>Patient Information</h6>
<table class="table table-sm">
<tr><td><strong>Name:</strong></td><td>${file.patient_name || 'N/A'}</td></tr>
<tr><td><strong>ID:</strong></td><td>${file.patient_id || 'N/A'}</td></tr>
<tr><td><strong>Birth Date:</strong></td><td>${file.patient_birth_date || 'N/A'}</td></tr>
<tr><td><strong>Sex:</strong></td><td>${file.patient_sex || 'N/A'}</td></tr>
</table>
</div>
<div class="col-md-6">
<h6>Study Information</h6>
<table class="table table-sm">
<tr><td><strong>Study Date:</strong></td><td>${file.study_date || 'N/A'}</td></tr>
<tr><td><strong>Study Time:</strong></td><td>${file.study_time || 'N/A'}</td></tr>
<tr><td><strong>Description:</strong></td><td>${file.study_description || 'N/A'}</td></tr>
<tr><td><strong>Modality:</strong></td><td>${file.modality || 'N/A'}</td></tr>
</table>
<h6>Series Information</h6>
<table class="table table-sm">
<tr><td><strong>Series Number:</strong></td><td>${file.series_number || 'N/A'}</td></tr>
<tr><td><strong>Description:</strong></td><td>${file.series_description || 'N/A'}</td></tr>
<tr><td><strong>Instance Number:</strong></td><td>${file.instance_number || 'N/A'}</td></tr>
<tr><td><strong>Slice Location:</strong></td><td>${file.slice_location || 'N/A'}</td></tr>
</table>
</div>
</div>
${file.thumbnail ? `
<div class="text-center mt-3">
<h6>Preview</h6>
<img src="${file.thumbnail}" alt="DICOM Preview" class="img-fluid" style="max-height: 300px;">
</div>
` : ''}
`;
document.getElementById('file-details-content').innerHTML = content;
}
function downloadFile(fileId) {
window.open(`/radiology/dicom/${fileId}/download/`, '_blank');
}
function analyzeFile(fileId) {
window.location.href = `/radiology/dicom/${fileId}/analyze/`;
}
function editFile(fileId) {
window.location.href = `/radiology/dicom/${fileId}/edit/`;
}
function deleteFile(fileId) {
currentFileId = fileId;
new bootstrap.Modal(document.getElementById('deleteModal')).show();
}
function confirmDeletion() {
const reason = document.getElementById('deletion-reason').value;
if (!reason.trim()) {
showAlert('Please provide a reason for deletion', 'warning');
return;
}
fetch(`/radiology/dicom/${currentFileId}/delete/`, {
method: 'DELETE',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({
reason: reason
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('DICOM file deleted successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error deleting DICOM file', 'danger');
}
})
.catch(error => {
showAlert('Error deleting DICOM file', 'danger');
});
bootstrap.Modal.getInstance(document.getElementById('deleteModal')).hide();
}
function bulkDownload() {
const selectedFiles = $('.file-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedFiles.length === 0) {
showAlert('Please select files to download', 'warning');
return;
}
const url = '/radiology/dicom/bulk-download/?files=' + selectedFiles.join(',');
window.open(url, '_blank');
}
function bulkAnalyze() {
const selectedFiles = $('.file-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedFiles.length === 0) {
showAlert('Please select files to analyze', 'warning');
return;
}
if (confirm(`Start analysis for ${selectedFiles.length} selected files?`)) {
fetch('/radiology/dicom/bulk-analyze/', {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_ids: selectedFiles
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert(`Analysis started for ${data.processed_count} files`, 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error starting analysis', 'danger');
}
})
.catch(error => {
showAlert('Error starting analysis', 'danger');
});
}
}
function bulkArchive() {
const selectedFiles = $('.file-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedFiles.length === 0) {
showAlert('Please select files to archive', 'warning');
return;
}
if (confirm(`Archive ${selectedFiles.length} selected files?`)) {
fetch('/radiology/dicom/bulk-archive/', {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_ids: selectedFiles
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert(`${data.archived_count} files archived successfully`, 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error archiving files', 'danger');
}
})
.catch(error => {
showAlert('Error archiving files', 'danger');
});
}
}
function bulkDelete() {
const selectedFiles = $('.file-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedFiles.length === 0) {
showAlert('Please select files to delete', 'warning');
return;
}
if (confirm(`Are you sure you want to delete ${selectedFiles.length} selected files? This action cannot be undone.`)) {
fetch('/radiology/dicom/bulk-delete/', {
method: 'DELETE',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_ids: selectedFiles
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert(`${data.deleted_count} files deleted successfully`, 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error deleting files', 'danger');
}
})
.catch(error => {
showAlert('Error deleting files', 'danger');
});
}
}
function openInViewer() {
if (currentFileId) {
window.open(`/radiology/dicom/${currentFileId}/viewer/`, '_blank');
}
}
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
</script>
{% endblock %}