Marwan Alwali be70e47e22 update
2025-08-30 09:45:26 +03:00

537 lines
21 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}QC Sample Details - {{ qc_sample.sample_id }}{% endblock %}
{% block extra_css %}
<style>
.qc-status-pending { color: #ffc107; }
.qc-status-in-progress { color: #17a2b8; }
.qc-status-completed { color: #28a745; }
.qc-status-failed { color: #dc3545; }
.qc-status-cancelled { color: #6c757d; }
.qc-level-level1 { color: #28a745; }
.qc-level-level2 { color: #ffc107; }
.qc-level-level3 { color: #fd7e14; }
.result-within-range { background-color: #d4edda; color: #155724; }
.result-out-of-range { background-color: #f8d7da; color: #721c24; }
.result-critical { background-color: #f5c6cb; color: #721c24; }
.info-card {
background: #f8f9fa;
border-left: 4px solid #007bff;
padding: 1rem;
margin-bottom: 1rem;
}
.result-card {
background: #fff;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.timeline {
position: relative;
padding-left: 2rem;
}
.timeline::before {
content: '';
position: absolute;
left: 0.5rem;
top: 0;
bottom: 0;
width: 2px;
background: #dee2e6;
}
.timeline-item {
position: relative;
margin-bottom: 1.5rem;
}
.timeline-item::before {
content: '';
position: absolute;
left: -0.75rem;
top: 0.25rem;
width: 0.75rem;
height: 0.75rem;
background: #007bff;
border-radius: 50%;
border: 2px solid #fff;
}
.chart-container {
position: relative;
height: 300px;
margin: 1rem 0;
}
@media (max-width: 768px) {
.timeline {
padding-left: 1.5rem;
}
.timeline-item::before {
left: -0.5rem;
}
}
</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 'laboratory:dashboard' %}">Laboratory</a></li>
<li class="breadcrumb-item"><a href="{% url 'laboratory:quality_control_list' %}">QC Samples</a></li>
<li class="breadcrumb-item active">{{ qc_sample.sample_id }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-vial me-2"></i>QC Sample: {{ qc_sample.sample_id }}
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'laboratory:quality_control_list' %}" class="btn btn-outline-secondary me-2">
<i class="fas fa-arrow-left me-1"></i>Back to List
</a>
{% if qc_sample.status == 'pending' or qc_sample.status == 'in_progress' %}
<a href="{% url 'laboratory:enter_qc_result' qc_sample.pk %}" class="btn btn-primary">
<i class="fas fa-edit me-1"></i>Enter Result
</a>
{% endif %}
</div>
</div>
<div class="row">
<!-- Sample Information -->
<div class="col-lg-8">
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-info-circle me-2"></i>Sample Information
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="info-card">
<h6 class="mb-2">
<i class="fas fa-vial me-2"></i>Sample Details
</h6>
<table class="table table-sm table-borderless">
<tr>
<td class="fw-bold">Sample ID:</td>
<td>{{ qc_sample.sample_id }}</td>
</tr>
<tr>
<td class="fw-bold">Test Type:</td>
<td>{{ qc_sample.test_type.name }}</td>
</tr>
<tr>
<td class="fw-bold">QC Level:</td>
<td>
<span class="badge qc-level-{{ qc_sample.qc_level }}">
{{ qc_sample.get_qc_level_display }}
</span>
</td>
</tr>
<tr>
<td class="fw-bold">Status:</td>
<td>
<span class="badge qc-status-{{ qc_sample.status }}">
{{ qc_sample.get_status_display }}
</span>
</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
<div class="info-card">
<h6 class="mb-2">
<i class="fas fa-calendar me-2"></i>Timing Information
</h6>
<table class="table table-sm table-borderless">
<tr>
<td class="fw-bold">Created:</td>
<td>{{ qc_sample.created_at|date:"M d, Y H:i" }}</td>
</tr>
<tr>
<td class="fw-bold">Run Date:</td>
<td>{{ qc_sample.run_date|date:"M d, Y" }}</td>
</tr>
<tr>
<td class="fw-bold">Run Time:</td>
<td>{{ qc_sample.run_time|time:"H:i" }}</td>
</tr>
{% if qc_sample.completed_at %}
<tr>
<td class="fw-bold">Completed:</td>
<td>{{ qc_sample.completed_at|date:"M d, Y H:i" }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="info-card">
<h6 class="mb-2">
<i class="fas fa-box me-2"></i>Lot Information
</h6>
<table class="table table-sm table-borderless">
<tr>
<td class="fw-bold">Lot Number:</td>
<td>{{ qc_sample.lot_number }}</td>
</tr>
<tr>
<td class="fw-bold">Expiry Date:</td>
<td>{{ qc_sample.expiry_date|date:"M d, Y" }}</td>
</tr>
<tr>
<td class="fw-bold">Expected Range:</td>
<td>{{ qc_sample.expected_range|default:"Not specified" }}</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
<div class="info-card">
<h6 class="mb-2">
<i class="fas fa-user me-2"></i>Personnel
</h6>
<table class="table table-sm table-borderless">
<tr>
<td class="fw-bold">Created By:</td>
<td>{{ qc_sample.created_by.get_full_name }}</td>
</tr>
<tr>
<td class="fw-bold">Technician:</td>
<td>{{ qc_sample.technician.get_full_name|default:"Not assigned" }}</td>
</tr>
{% if qc_sample.reviewed_by %}
<tr>
<td class="fw-bold">Reviewed By:</td>
<td>{{ qc_sample.reviewed_by.get_full_name }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
</div>
{% if qc_sample.comments %}
<div class="info-card">
<h6 class="mb-2">
<i class="fas fa-comment me-2"></i>Comments
</h6>
<p class="mb-0">{{ qc_sample.comments }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Results Section -->
{% if qc_sample.result_value %}
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-line me-2"></i>QC Results
</h5>
</div>
<div class="card-body">
<div class="result-card result-{{ qc_sample.result_status }}">
<div class="row align-items-center">
<div class="col-md-4">
<h3 class="mb-1">{{ qc_sample.result_value }} {{ qc_sample.unit }}</h3>
<p class="text-muted mb-0">Measured Value</p>
</div>
<div class="col-md-4">
<h5 class="mb-1">{{ qc_sample.expected_range }}</h5>
<p class="text-muted mb-0">Expected Range</p>
</div>
<div class="col-md-4">
<span class="badge result-{{ qc_sample.result_status }} fs-6">
{{ qc_sample.get_result_status_display }}
</span>
</div>
</div>
{% if qc_sample.result_comments %}
<hr>
<div>
<h6>Result Comments:</h6>
<p class="mb-0">{{ qc_sample.result_comments }}</p>
</div>
{% endif %}
</div>
<!-- Trend Chart Placeholder -->
<div class="chart-container">
<canvas id="qcTrendChart"></canvas>
</div>
</div>
</div>
{% endif %}
<!-- Activity Timeline -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-history me-2"></i>Activity Timeline
</h5>
</div>
<div class="card-body">
<div class="timeline">
{% for activity in qc_sample.activities.all %}
<div class="timeline-item">
<div class="d-flex justify-content-between">
<div>
<h6 class="mb-1">{{ activity.action }}</h6>
<p class="text-muted mb-1">{{ activity.description }}</p>
<small class="text-muted">
by {{ activity.user.get_full_name }} - {{ activity.created_at|date:"M d, Y H:i" }}
</small>
</div>
<div class="text-end">
<span class="badge bg-secondary">{{ activity.get_action_display }}</span>
</div>
</div>
</div>
{% empty %}
<div class="text-muted text-center py-3">
<i class="fas fa-history fa-2x mb-2"></i>
<p>No activity recorded yet.</p>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-bolt me-2"></i>Quick Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if qc_sample.status == 'pending' or qc_sample.status == 'in_progress' %}
<a href="{% url 'laboratory:enter_qc_result' qc_sample.pk %}" class="btn btn-primary">
<i class="fas fa-edit me-2"></i>Enter Result
</a>
{% endif %}
<button type="button" class="btn btn-outline-info" onclick="printQCLabel()">
<i class="fas fa-print me-2"></i>Print Label
</button>
<button type="button" class="btn btn-outline-success" onclick="exportQCData()">
<i class="fas fa-download me-2"></i>Export Data
</button>
{% if qc_sample.status == 'pending' %}
<button type="button" class="btn btn-outline-danger" onclick="cancelQCSample()">
<i class="fas fa-times me-2"></i>Cancel Sample
</button>
{% endif %}
{% if qc_sample.status == 'completed' and not qc_sample.reviewed_by %}
<button type="button" class="btn btn-outline-warning" onclick="reviewQCSample()">
<i class="fas fa-check me-2"></i>Review & Approve
</button>
{% endif %}
</div>
</div>
</div>
<!-- Related QC Samples -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-link me-2"></i>Related QC Samples
</h5>
</div>
<div class="card-body">
{% for related_sample in related_qc_samples %}
<div class="d-flex align-items-center mb-2">
<i class="fas fa-vial me-2 text-muted"></i>
<div class="flex-grow-1">
<a href="{% url 'laboratory:qc_sample_detail' related_sample.pk %}" class="text-decoration-none">
{{ related_sample.sample_id }}
</a>
<small class="d-block text-muted">{{ related_sample.run_date|date:"M d, Y" }}</small>
</div>
<span class="badge qc-status-{{ related_sample.status }}">
{{ related_sample.get_status_display }}
</span>
</div>
{% empty %}
<div class="text-muted text-center py-3">
<i class="fas fa-link fa-2x mb-2"></i>
<p>No related QC samples found.</p>
</div>
{% endfor %}
</div>
</div>
<!-- QC Statistics -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-bar me-2"></i>QC Statistics
</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6">
<h4 class="text-success">{{ qc_stats.pass_rate }}%</h4>
<small class="text-muted">Pass Rate</small>
</div>
<div class="col-6">
<h4 class="text-primary">{{ qc_stats.total_runs }}</h4>
<small class="text-muted">Total Runs</small>
</div>
</div>
<hr>
<div class="row text-center">
<div class="col-6">
<h5 class="text-warning">{{ qc_stats.out_of_range }}</h5>
<small class="text-muted">Out of Range</small>
</div>
<div class="col-6">
<h5 class="text-danger">{{ qc_stats.failed }}</h5>
<small class="text-muted">Failed</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/chart.js/dist/chart.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize QC Trend Chart
{% if qc_sample.result_value %}
initializeQCTrendChart();
{% endif %}
});
function initializeQCTrendChart() {
const ctx = document.getElementById('qcTrendChart').getContext('2d');
// Sample data - replace with actual data from backend
const chartData = {
labels: {{ trend_dates|safe }},
datasets: [{
label: 'QC Results',
data: {{ trend_values|safe }},
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1
}, {
label: 'Upper Limit',
data: {{ upper_limits|safe }},
borderColor: 'rgb(255, 99, 132)',
borderDash: [5, 5],
fill: false
}, {
label: 'Lower Limit',
data: {{ lower_limits|safe }},
borderColor: 'rgb(255, 99, 132)',
borderDash: [5, 5],
fill: false
}]
};
new Chart(ctx, {
type: 'line',
data: chartData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'QC Trend Analysis - {{ qc_sample.test_type.name }}'
},
legend: {
position: 'top'
}
},
scales: {
y: {
beginAtZero: false,
title: {
display: true,
text: 'Value ({{ qc_sample.unit }})'
}
},
x: {
title: {
display: true,
text: 'Date'
}
}
}
}
});
}
function printQCLabel() {
window.open('{% url "laboratory:print_qc_label" qc_sample.pk %}', '_blank');
}
function exportQCData() {
window.location.href = '{% url "laboratory:export_qc_data" qc_sample.pk %}';
}
function cancelQCSample() {
if (confirm('Are you sure you want to cancel this QC sample?')) {
$.ajax({
url: '{% url "laboratory:cancel_qc_sample" qc_sample.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error cancelling QC sample: ' + response.message);
}
},
error: function() {
alert('Error cancelling QC sample.');
}
});
}
}
function reviewQCSample() {
window.location.href = '{% url "laboratory:review_qc_sample" qc_sample.pk %}';
}
</script>
{% endblock %}