551 lines
24 KiB
HTML
551 lines
24 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Specimen Details - {{ object.specimen_id }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'laboratory:dashboard' %}">Laboratory</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'laboratory:specimen_list' %}">Specimens</a></li>
|
|
<li class="breadcrumb-item active">{{ object.specimen_id }}</li>
|
|
</ol>
|
|
<h1 class="page-header mb-0">Specimen Details - {{ object.specimen_id }}</h1>
|
|
</div>
|
|
<div class="ms-auto">
|
|
<div class="btn-group">
|
|
<a href="{% url 'laboratory:specimen_update' object.pk %}" class="btn btn-primary">
|
|
<i class="fas fa-edit me-2"></i>Edit Specimen
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="printLabel()">
|
|
<i class="fas fa-print text-primary me-2"></i>Print Label
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="trackSpecimen()">
|
|
<i class="fas fa-route text-info me-2"></i>Track Specimen
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="addTest()">
|
|
<i class="fas fa-plus text-success me-2"></i>Add Test
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#" onclick="generateReport()">
|
|
<i class="fas fa-chart-line me-2"></i>Generate Report
|
|
</a></li>
|
|
{# <li><a class="dropdown-item text-danger" href="{% url 'laboratory:specimen_confirm_delete' object.pk %}">#}
|
|
{# <i class="fas fa-trash me-2"></i>Delete Specimen#}
|
|
{# </a></li>#}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xl-8">
|
|
<!-- Specimen Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-vial me-2"></i>
|
|
Specimen Information
|
|
</h4>
|
|
<div class="card-toolbar">
|
|
<span class="badge bg-{% if object.status == 'collected' %}success{% elif object.status == 'processing' %}warning{% elif object.status == 'completed' %}info{% elif object.status == 'rejected' %}danger{% else %}secondary{% endif %} fs-6">
|
|
{{ object.get_status_display }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold">Specimen ID:</td>
|
|
<td>{{ object.specimen_id }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Patient:</td>
|
|
<td>
|
|
<a href="{% url 'patients:patient_detail' object.patient.pk %}">
|
|
{{ object.patient.get_full_name }}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">MRN:</td>
|
|
<td>{{ object.patient.mrn }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Type:</td>
|
|
<td>
|
|
|
|
<i class="fas
|
|
fa-{% if specimen.specimen_type == 'BLOOD' %}tint text-danger
|
|
{% elif specimen.specimen_type == 'URINE' %}flask text-warning
|
|
{% elif specimen.specimen_type == 'SPUTUM' %}lungs text-info
|
|
{% elif specimen.specimen_type == 'PLASMA' %}tint text-yellow
|
|
{% elif specimen.specimen_type == 'SERUM' %}bottle-droplet text-info
|
|
{% elif specimen.specimen_type == 'STOOL' %}poop text-cyan
|
|
{% elif specimen.specimen_type == 'CSF' %}eye text-primary
|
|
{% elif specimen.specimen_type == 'SWAB' %}hand text-secondary
|
|
{% elif specimen.specimen_type == 'TISSUE' %}microscope text-green
|
|
{% elif specimen.specimen_type == 'FLUID' %}droplet text-info
|
|
{% elif specimen.specimen_type == 'SALIVA' %}lungs text-info
|
|
{% else %}vial-vertical text-secondary{% endif %}"></i>
|
|
<span class="fw-bold">{{ specimen.get_specimen_type_display }}</span>
|
|
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Collection Date:</td>
|
|
<td>{{ object.collected_datetime|date:"M d, Y g:i A" }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold">Collected By:</td>
|
|
<td>{{ object.collected_by.get_full_name }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Collection Site:</td>
|
|
<td>{{ object.collection_site|default:"Not specified" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Volume:</td>
|
|
<td>{{ object.volume|default:"Not specified" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Container:</td>
|
|
<td>{{ object.container_type|default:"Standard" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Priority:</td>
|
|
<td>
|
|
<span class="badge bg-{% if object.order.priority == 'STAT' %}danger{% elif object.order.priority == 'URGENT' %}warning{% else %}primary{% endif %}">
|
|
{{ object.order.get_priority_display }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{% if object.collection_notes %}
|
|
<div class="mt-3">
|
|
<h6>Collection Notes:</h6>
|
|
<div class="alert alert-info">
|
|
{{ object.collection_notes }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Associated Tests -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-flask me-2"></i>
|
|
Associated Tests
|
|
</h4>
|
|
<div class="card-toolbar">
|
|
<button type="button" class="btn btn-sm btn-primary" onclick="addTest()">
|
|
<i class="fas fa-plus me-2"></i>Add Test
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if associated_tests %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Test Name</th>
|
|
<th>Code</th>
|
|
<th>Status</th>
|
|
<th>Ordered Date</th>
|
|
<th>Result</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for test in associated_tests %}
|
|
<tr>
|
|
<td>
|
|
<div class="fw-bold">{{ test.test.name }}</div>
|
|
<div class="small text-muted">{{ test.test.category }}</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-light text-dark">{{ test.test.code }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if test.status == 'completed' %}success{% elif test.status == 'in_progress' %}warning{% elif test.status == 'pending' %}info{% else %}secondary{% endif %}">
|
|
{{ test.get_status_display }}
|
|
</span>
|
|
</td>
|
|
<td>{{ test.ordered_date|date:"M d, Y" }}</td>
|
|
<td>
|
|
{% if test.result %}
|
|
<span class="fw-bold">{{ test.result.result_value }} {{ test.result.unit|default:"" }}</span>
|
|
{% if test.result.abnormal_flag %}
|
|
<span class="badge bg-warning ms-1">{{ test.result.get_abnormal_flag_display }}</span>
|
|
{% endif %}
|
|
{% else %}
|
|
<span class="text-muted">Pending</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'laboratory:lab_order_detail' test.pk %}"
|
|
class="btn btn-outline-primary"
|
|
title="View Order">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
{% if test.result %}
|
|
<a href="{% url 'laboratory:lab_result_detail' test.result.pk %}"
|
|
class="btn btn-outline-success"
|
|
title="View Result">
|
|
<i class="fas fa-flask"></i>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-flask fa-3x mb-3"></i>
|
|
<h5>No Tests Associated</h5>
|
|
<p>No tests have been ordered for this specimen yet.</p>
|
|
<button type="button" class="btn btn-primary" onclick="addTest()">
|
|
<i class="fas fa-plus me-2"></i>Add First Test
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tracking History -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="card-title">
|
|
<i class="fas fa-route me-2"></i>
|
|
Tracking History
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if tracking_history %}
|
|
<div class="timeline">
|
|
{% for event in tracking_history %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-{% if event.status == 'collected' %}success{% elif event.status == 'processing' %}warning{% elif event.status == 'completed' %}info{% else %}secondary{% endif %}">
|
|
<i class="fas fa-{% if event.status == 'collected' %}check{% elif event.status == 'processing' %}cog{% elif event.status == 'completed' %}flag{% else %}circle{% endif %}"></i>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="d-flex justify-content-between">
|
|
<h6 class="mb-1">{{ event.get_status_display }}</h6>
|
|
<small class="text-muted">{{ event.timestamp|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
<p class="mb-1">{{ event.description|default:"Status updated" }}</p>
|
|
<small class="text-muted">By: {{ event.user.get_full_name|default:"System" }}</small>
|
|
{% if event.location %}
|
|
<div class="small text-muted">Location: {{ event.location }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-route fa-3x mb-3"></i>
|
|
<h5>No Tracking History</h5>
|
|
<p>No tracking events recorded for this specimen.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-4">
|
|
<!-- Quick Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-bolt me-2"></i>
|
|
Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<button type="button" class="btn btn-success" onclick="updateStatus('processing')">
|
|
<i class="fas fa-play me-2"></i>Start Processing
|
|
</button>
|
|
<button type="button" class="btn btn-warning" onclick="updateStatus('on_hold')">
|
|
<i class="fas fa-pause me-2"></i>Put on Hold
|
|
</button>
|
|
<button type="button" class="btn btn-info" onclick="updateStatus('completed')">
|
|
<i class="fas fa-check me-2"></i>Mark Complete
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="printLabel()">
|
|
<i class="fas fa-print me-2"></i>Print Label
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="addNote()">
|
|
<i class="fas fa-sticky-note me-2"></i>Add Note
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Patient Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-user-injured me-2"></i>
|
|
Patient Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<strong>Name:</strong> {{ object.patient.get_full_name }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>DOB:</strong> {{ object.patient.date_of_birth|date:"M d, Y" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Age:</strong> {{ object.patient.age }} years
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Gender:</strong> {{ object.patient.get_gender_display }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>MRN:</strong> {{ object.patient.mrn }}
|
|
</div>
|
|
<div>
|
|
<a href="{% url 'patients:patient_detail' object.patient.pk %}" class="btn btn-sm btn-outline-primary">
|
|
<i class="fas fa-user me-2"></i>View Patient
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Collection Details -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-clipboard-check me-2"></i>
|
|
Collection Details
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<strong>Collection Method:</strong> {{ object.collection_method|default:"Standard" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Fasting Status:</strong>
|
|
{% if object.fasting_status %}
|
|
<span class="badge bg-success">Fasting</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Non-fasting</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Temperature:</strong> {{ object.storage_temperature|default:"Room temp" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Condition:</strong> {{ object.condition|default:"Good" }}
|
|
</div>
|
|
{% if object.expiry_date %}
|
|
<div>
|
|
<strong>Expires:</strong> {{ object.expiry_date|date:"M d, Y" }}
|
|
{% if object.days_until_expiry <= 1 %}
|
|
<span class="badge bg-danger ms-1">Expires Soon</span>
|
|
{% elif object.days_until_expiry <= 3 %}
|
|
<span class="badge bg-warning ms-1">Expiring</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quality Control -->
|
|
{% if object.quality_issues %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
Quality Issues
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% for issue in object.quality_issues %}
|
|
<div class="alert alert-warning">
|
|
<strong>{{ issue.issue_type }}:</strong> {{ issue.description }}
|
|
<div class="small text-muted">Reported: {{ issue.reported_date|date:"M d, Y g:i A" }}</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- System Information -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-info-circle me-2"></i>
|
|
System Information
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<strong>Specimen ID:</strong> {{ object.pk }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Created:</strong> {{ object.created_at|date:"M d, Y g:i A" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Last Updated:</strong> {{ object.updated_at|date:"M d, Y g:i A" }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Barcode:</strong> {{ object.barcode|default:"Not assigned" }}
|
|
</div>
|
|
<div>
|
|
<strong>Location:</strong> {{ object.current_location|default:"Lab" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Update Modal -->
|
|
<div class="modal fade" id="statusUpdateModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Update Specimen Status</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="statusUpdateForm">
|
|
{% csrf_token %}
|
|
<input type="hidden" id="newStatus" name="status">
|
|
<div class="mb-3">
|
|
<label for="statusNotes" class="form-label">Notes</label>
|
|
<textarea class="form-control" id="statusNotes" name="notes" rows="3" placeholder="Enter any notes about this status change..."></textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="statusLocation" class="form-label">Current Location</label>
|
|
<input type="text" class="form-control" id="statusLocation" name="location" placeholder="Enter current location">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="confirmStatusUpdate()">Update Status</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function updateStatus(status) {
|
|
document.getElementById('newStatus').value = status;
|
|
const modal = new bootstrap.Modal(document.getElementById('statusUpdateModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function confirmStatusUpdate() {
|
|
const form = document.getElementById('statusUpdateForm');
|
|
const formData = new FormData(form);
|
|
|
|
// In a real implementation, this would submit via AJAX
|
|
console.log('Updating status:', formData.get('status'));
|
|
|
|
// Close modal and refresh page
|
|
bootstrap.Modal.getInstance(document.getElementById('statusUpdateModal')).hide();
|
|
location.reload();
|
|
}
|
|
|
|
function addTest() {
|
|
// In a real implementation, this would open a test selection modal
|
|
alert('Add test functionality would be implemented here.');
|
|
}
|
|
|
|
function printLabel() {
|
|
// In a real implementation, this would print a specimen label
|
|
window.print();
|
|
}
|
|
|
|
function trackSpecimen() {
|
|
// In a real implementation, this would show detailed tracking
|
|
alert('Specimen tracking functionality would be implemented here.');
|
|
}
|
|
|
|
function addNote() {
|
|
// In a real implementation, this would open a note modal
|
|
const note = prompt('Enter note:');
|
|
if (note) {
|
|
console.log('Adding note:', note);
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
function generateReport() {
|
|
// In a real implementation, this would generate a specimen report
|
|
alert('Generate report functionality would be implemented here.');
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 30px;
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.timeline-item:not(:last-child)::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -19px;
|
|
top: 30px;
|
|
height: calc(100% + 10px);
|
|
width: 2px;
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.timeline-marker {
|
|
position: absolute;
|
|
left: -30px;
|
|
top: 0;
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.timeline-content {
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
border-left: 3px solid #dee2e6;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|