674 lines
28 KiB
HTML
674 lines
28 KiB
HTML
{% extends 'base.html' %}
|
||
{% load static %}
|
||
|
||
{% block title %}Enter QC Result - {{ qc_sample.sample_id }}{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.result-entry-card {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 0.375rem;
|
||
padding: 1.5rem;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.sample-info-card {
|
||
background: #e3f2fd;
|
||
border-left: 4px solid #2196f3;
|
||
padding: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.result-status-within { background-color: #d4edda; color: #155724; }
|
||
.result-status-out-of-range { background-color: #f8d7da; color: #721c24; }
|
||
.result-status-critical { background-color: #f5c6cb; color: #721c24; }
|
||
|
||
.validation-indicator {
|
||
padding: 0.5rem;
|
||
border-radius: 0.25rem;
|
||
margin-top: 0.5rem;
|
||
display: none;
|
||
}
|
||
|
||
.validation-indicator.valid {
|
||
background-color: #d4edda;
|
||
color: #155724;
|
||
display: block;
|
||
}
|
||
|
||
.validation-indicator.invalid {
|
||
background-color: #f8d7da;
|
||
color: #721c24;
|
||
display: block;
|
||
}
|
||
|
||
.result-input {
|
||
font-size: 1.25rem;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.expected-range-display {
|
||
background: #fff3cd;
|
||
border: 1px solid #ffeaa7;
|
||
border-radius: 0.25rem;
|
||
padding: 0.75rem;
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.quick-actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.trend-chart-container {
|
||
height: 200px;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.result-input {
|
||
font-size: 1rem;
|
||
padding: 0.75rem;
|
||
}
|
||
|
||
.quick-actions {
|
||
justify-content: center;
|
||
}
|
||
}
|
||
</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:qc_sample_list' %}">QC Samples</a></li>
|
||
<li class="breadcrumb-item"><a href="{% url 'laboratory:qc_sample_detail' qc_sample.pk %}">{{ qc_sample.sample_id }}</a></li>
|
||
<li class="breadcrumb-item active">Enter Result</li>
|
||
</ol>
|
||
<h1 class="page-header mb-0">
|
||
<i class="fas fa-edit me-2"></i>Enter QC Result
|
||
</h1>
|
||
</div>
|
||
<div class="ms-auto">
|
||
<a href="{% url 'laboratory:qc_sample_detail' qc_sample.pk %}" class="btn btn-outline-secondary">
|
||
<i class="fas fa-arrow-left me-1"></i>Back to Sample
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-lg-8">
|
||
<!-- Sample Information -->
|
||
<div class="sample-info-card">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<h6 class="mb-2">
|
||
<i class="fas fa-vial me-2"></i>{{ qc_sample.sample_id }}
|
||
</h6>
|
||
<p class="mb-1"><strong>Test:</strong> {{ qc_sample.test_type.name }}</p>
|
||
<p class="mb-1"><strong>QC Level:</strong> {{ qc_sample.get_qc_level_display }}</p>
|
||
<p class="mb-0"><strong>Lot:</strong> {{ qc_sample.lot_number }}</p>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<p class="mb-1"><strong>Run Date:</strong> {{ qc_sample.run_date|date:"M d, Y" }}</p>
|
||
<p class="mb-1"><strong>Run Time:</strong> {{ qc_sample.run_time|time:"H:i" }}</p>
|
||
<p class="mb-0"><strong>Expected Range:</strong> {{ qc_sample.expected_range|default:"Not specified" }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Result Entry Form -->
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="card-title mb-0">
|
||
<i class="fas fa-calculator me-2"></i>Result Entry
|
||
</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<form id="resultForm" method="post">
|
||
{% csrf_token %}
|
||
|
||
<div class="result-entry-card">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold">Result Value *</label>
|
||
<div class="input-group">
|
||
<input type="number" class="form-control result-input"
|
||
name="result_value" id="result-value-input"
|
||
value="{{ qc_sample.result_value|default:'' }}"
|
||
step="0.001" placeholder="Enter result" required>
|
||
<span class="input-group-text">{{ qc_sample.test_type.unit|default:"" }}</span>
|
||
</div>
|
||
<div id="validation-indicator" class="validation-indicator"></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="form-group mb-3">
|
||
<label class="form-label fw-bold">Result Status</label>
|
||
<select class="form-select" name="result_status" id="result-status-select">
|
||
<option value="within_range" {% if qc_sample.result_status == 'within_range' %}selected{% endif %}>
|
||
Within Range
|
||
</option>
|
||
<option value="out_of_range" {% if qc_sample.result_status == 'out_of_range' %}selected{% endif %}>
|
||
Out of Range
|
||
</option>
|
||
<option value="critical" {% if qc_sample.result_status == 'critical' %}selected{% endif %}>
|
||
Critical
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if qc_sample.expected_range %}
|
||
<div class="expected-range-display">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<span><strong>Expected Range:</strong> {{ qc_sample.expected_range }}</span>
|
||
<span id="range-status" class="badge"></span>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="quick-actions">
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="setTargetValue()">
|
||
<i class="fas fa-bullseye me-1"></i>Use Target
|
||
</button>
|
||
<button type="button" class="btn btn-outline-info btn-sm" onclick="showCalculator()">
|
||
<i class="fas fa-calculator me-1"></i>Calculator
|
||
</button>
|
||
<button type="button" class="btn btn-outline-warning btn-sm" onclick="flagOutlier()">
|
||
<i class="fas fa-exclamation-triangle me-1"></i>Flag Outlier
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="form-group mb-3">
|
||
<label class="form-label">Instrument Used</label>
|
||
<select class="form-select" name="instrument">
|
||
<option value="">Select instrument...</option>
|
||
{% for instrument in instruments %}
|
||
<option value="{{ instrument.id }}"
|
||
{% if qc_sample.instrument_id == instrument.id %}selected{% endif %}>
|
||
{{ instrument.name }} ({{ instrument.model }})
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="form-group mb-3">
|
||
<label class="form-label">Technician</label>
|
||
<select class="form-select" name="technician">
|
||
<option value="{{ request.user.id }}" selected>
|
||
{{ request.user.get_full_name }} (Current User)
|
||
</option>
|
||
{% for tech in technicians %}
|
||
{% if tech.id != request.user.id %}
|
||
<option value="{{ tech.id }}">
|
||
{{ tech.get_full_name }}
|
||
</option>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group mb-3">
|
||
<label class="form-label">Result Comments</label>
|
||
<textarea class="form-control" name="result_comments" rows="3"
|
||
placeholder="Any observations, notes, or comments about this result...">{{ qc_sample.result_comments|default:'' }}</textarea>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input" type="checkbox" name="requires_review"
|
||
id="requires-review" {% if qc_sample.result_status == 'out_of_range' or qc_sample.result_status == 'critical' %}checked{% endif %}>
|
||
<label class="form-check-label" for="requires-review">
|
||
Requires supervisor review
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input" type="checkbox" name="notify_supervisor"
|
||
id="notify-supervisor">
|
||
<label class="form-check-label" for="notify-supervisor">
|
||
Notify supervisor immediately
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex justify-content-between">
|
||
<div>
|
||
<a href="{% url 'laboratory:qc_sample_detail' qc_sample.pk %}" class="btn btn-outline-secondary">
|
||
<i class="fas fa-times me-1"></i>Cancel
|
||
</a>
|
||
</div>
|
||
<div>
|
||
<button type="button" class="btn btn-outline-primary me-2" onclick="saveAsDraft()">
|
||
<i class="fas fa-save me-1"></i>Save as Draft
|
||
</button>
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fas fa-check me-1"></i>Submit Result
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sidebar -->
|
||
<div class="col-lg-4">
|
||
<!-- QC Trend Chart -->
|
||
<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 Trend
|
||
</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="trend-chart-container">
|
||
<canvas id="qcTrendChart"></canvas>
|
||
</div>
|
||
<div class="text-center">
|
||
<small class="text-muted">Last 10 QC results for {{ qc_sample.test_type.name }}</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recent Results -->
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="card-title mb-0">
|
||
<i class="fas fa-history me-2"></i>Recent Results
|
||
</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
{% for recent_result in recent_results %}
|
||
<div class="d-flex align-items-center mb-2">
|
||
<div class="flex-grow-1">
|
||
<div class="fw-bold">{{ recent_result.sample_id }}</div>
|
||
<small class="text-muted">{{ recent_result.run_date|date:"M d, Y" }}</small>
|
||
</div>
|
||
<div class="text-end">
|
||
<div class="fw-bold">{{ recent_result.result_value }} {{ recent_result.unit }}</div>
|
||
<span class="badge result-status-{{ recent_result.result_status }}">
|
||
{{ recent_result.get_result_status_display }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
{% empty %}
|
||
<div class="text-muted text-center py-3">
|
||
<i class="fas fa-history fa-2x mb-2"></i>
|
||
<p>No recent results</p>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- QC Guidelines -->
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="card-title mb-0">
|
||
<i class="fas fa-info-circle me-2"></i>QC Guidelines
|
||
</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<h6>Result Interpretation:</h6>
|
||
<ul class="list-unstyled">
|
||
<li class="mb-1">
|
||
<span class="badge result-status-within me-2">Within Range</span>
|
||
Acceptable result
|
||
</li>
|
||
<li class="mb-1">
|
||
<span class="badge result-status-out-of-range me-2">Out of Range</span>
|
||
Requires investigation
|
||
</li>
|
||
<li class="mb-1">
|
||
<span class="badge result-status-critical me-2">Critical</span>
|
||
Immediate action required
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<h6>Actions Required:</h6>
|
||
<ul class="list-unstyled">
|
||
<li><i class="fas fa-check text-success me-2"></i>Verify instrument calibration</li>
|
||
<li><i class="fas fa-check text-success me-2"></i>Check control material expiry</li>
|
||
<li><i class="fas fa-check text-success me-2"></i>Review procedure compliance</li>
|
||
<li><i class="fas fa-check text-success me-2"></i>Document corrective actions</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quick Reference -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="card-title mb-0">
|
||
<i class="fas fa-book me-2"></i>Quick Reference
|
||
</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-2">
|
||
<strong>Target Value:</strong> {{ qc_sample.target_value|default:"Not set" }}
|
||
</div>
|
||
<div class="mb-2">
|
||
<strong>CV Limit:</strong> {{ qc_sample.cv_limit|default:"Not set" }}%
|
||
</div>
|
||
<div class="mb-2">
|
||
<strong>SD Limit:</strong> {{ qc_sample.sd_limit|default:"Not set" }}
|
||
</div>
|
||
<div class="mb-2">
|
||
<strong>Westgard Rules:</strong>
|
||
<a href="#" onclick="showWestgardRules()">View Rules</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Calculator Modal -->
|
||
<div class="modal fade" id="calculatorModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">
|
||
<i class="fas fa-calculator me-2"></i>Calculator
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="calculator">
|
||
<input type="text" class="form-control mb-3" id="calculator-display" readonly>
|
||
<div class="row g-2">
|
||
<div class="col-3"><button class="btn btn-outline-secondary w-100" onclick="clearCalculator()">C</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-secondary w-100" onclick="appendToCalculator('/')">/</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-secondary w-100" onclick="appendToCalculator('*')">×</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-secondary w-100" onclick="deleteLast()">⌫</button></div>
|
||
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('7')">7</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('8')">8</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('9')">9</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-secondary w-100" onclick="appendToCalculator('-')">-</button></div>
|
||
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('4')">4</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('5')">5</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('6')">6</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-secondary w-100" onclick="appendToCalculator('+')">+</button></div>
|
||
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('1')">1</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('2')">2</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('3')">3</button></div>
|
||
<div class="col-3 row-span-2"><button class="btn btn-success w-100 h-100" onclick="calculateResult()">=</button></div>
|
||
|
||
<div class="col-6"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('0')">0</button></div>
|
||
<div class="col-3"><button class="btn btn-outline-primary w-100" onclick="appendToCalculator('.')">.</button></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-primary" onclick="useCalculatorResult()">Use Result</button>
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script src="{% static 'assets/plugins/chart.js/dist/chart.min.js' %}"></script>
|
||
|
||
<script>
|
||
let calculatorValue = '';
|
||
let expectedRange = null;
|
||
|
||
$(document).ready(function() {
|
||
// Parse expected range if available
|
||
const rangeText = '{{ qc_sample.expected_range|default:"" }}';
|
||
if (rangeText) {
|
||
parseExpectedRange(rangeText);
|
||
}
|
||
|
||
// Initialize trend chart
|
||
initializeTrendChart();
|
||
|
||
// Real-time validation
|
||
$('#result-value-input').on('input', function() {
|
||
validateResult();
|
||
updateResultStatus();
|
||
});
|
||
|
||
// Form submission
|
||
$('#resultForm').on('submit', function(e) {
|
||
if (!validateForm()) {
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
});
|
||
|
||
// Initial validation
|
||
validateResult();
|
||
});
|
||
|
||
function parseExpectedRange(rangeText) {
|
||
// Parse range like "4.5-6.0" or "4.5 - 6.0 mg/dL"
|
||
const match = rangeText.match(/(\d+\.?\d*)\s*-\s*(\d+\.?\d*)/);
|
||
if (match) {
|
||
expectedRange = {
|
||
min: parseFloat(match[1]),
|
||
max: parseFloat(match[2])
|
||
};
|
||
}
|
||
}
|
||
|
||
function validateResult() {
|
||
const resultValue = parseFloat($('#result-value-input').val());
|
||
const indicator = $('#validation-indicator');
|
||
const rangeStatus = $('#range-status');
|
||
|
||
if (isNaN(resultValue)) {
|
||
indicator.removeClass('valid invalid').hide();
|
||
rangeStatus.removeClass().addClass('badge');
|
||
return;
|
||
}
|
||
|
||
if (expectedRange) {
|
||
if (resultValue >= expectedRange.min && resultValue <= expectedRange.max) {
|
||
indicator.removeClass('invalid').addClass('valid')
|
||
.html('<i class="fas fa-check me-2"></i>Result is within expected range');
|
||
rangeStatus.removeClass().addClass('badge bg-success').text('Within Range');
|
||
} else {
|
||
indicator.removeClass('valid').addClass('invalid')
|
||
.html('<i class="fas fa-exclamation-triangle me-2"></i>Result is outside expected range');
|
||
rangeStatus.removeClass().addClass('badge bg-danger').text('Out of Range');
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateResultStatus() {
|
||
const resultValue = parseFloat($('#result-value-input').val());
|
||
const statusSelect = $('#result-status-select');
|
||
|
||
if (isNaN(resultValue) || !expectedRange) return;
|
||
|
||
if (resultValue >= expectedRange.min && resultValue <= expectedRange.max) {
|
||
statusSelect.val('within_range');
|
||
$('#requires-review').prop('checked', false);
|
||
} else {
|
||
const deviation = Math.abs(resultValue - (expectedRange.min + expectedRange.max) / 2);
|
||
const rangeSize = expectedRange.max - expectedRange.min;
|
||
|
||
if (deviation > rangeSize) {
|
||
statusSelect.val('critical');
|
||
$('#requires-review').prop('checked', true);
|
||
$('#notify-supervisor').prop('checked', true);
|
||
} else {
|
||
statusSelect.val('out_of_range');
|
||
$('#requires-review').prop('checked', true);
|
||
}
|
||
}
|
||
}
|
||
|
||
function validateForm() {
|
||
const resultValue = $('#result-value-input').val();
|
||
|
||
if (!resultValue) {
|
||
alert('Please enter a result value.');
|
||
$('#result-value-input').focus();
|
||
return false;
|
||
}
|
||
|
||
if (isNaN(parseFloat(resultValue))) {
|
||
alert('Please enter a valid numeric result.');
|
||
$('#result-value-input').focus();
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
function setTargetValue() {
|
||
const targetValue = {{ qc_sample.target_value|default:"null" }};
|
||
if (targetValue) {
|
||
$('#result-value-input').val(targetValue).trigger('input');
|
||
} else {
|
||
alert('No target value set for this QC sample.');
|
||
}
|
||
}
|
||
|
||
function showCalculator() {
|
||
calculatorValue = $('#result-value-input').val() || '';
|
||
$('#calculator-display').val(calculatorValue);
|
||
$('#calculatorModal').modal('show');
|
||
}
|
||
|
||
function appendToCalculator(value) {
|
||
calculatorValue += value;
|
||
$('#calculator-display').val(calculatorValue);
|
||
}
|
||
|
||
function clearCalculator() {
|
||
calculatorValue = '';
|
||
$('#calculator-display').val('');
|
||
}
|
||
|
||
function deleteLast() {
|
||
calculatorValue = calculatorValue.slice(0, -1);
|
||
$('#calculator-display').val(calculatorValue);
|
||
}
|
||
|
||
function calculateResult() {
|
||
try {
|
||
const result = eval(calculatorValue);
|
||
calculatorValue = result.toString();
|
||
$('#calculator-display').val(calculatorValue);
|
||
} catch (e) {
|
||
alert('Invalid calculation');
|
||
}
|
||
}
|
||
|
||
function useCalculatorResult() {
|
||
$('#result-value-input').val(calculatorValue).trigger('input');
|
||
$('#calculatorModal').modal('hide');
|
||
}
|
||
|
||
function flagOutlier() {
|
||
$('#result-status-select').val('critical');
|
||
$('#requires-review').prop('checked', true);
|
||
$('#notify-supervisor').prop('checked', true);
|
||
|
||
const comments = $('textarea[name="result_comments"]');
|
||
const currentComments = comments.val();
|
||
const outlierNote = 'FLAGGED AS OUTLIER: ';
|
||
|
||
if (!currentComments.includes(outlierNote)) {
|
||
comments.val(outlierNote + currentComments);
|
||
}
|
||
}
|
||
|
||
function saveAsDraft() {
|
||
const form = $('#resultForm');
|
||
const originalAction = form.attr('action');
|
||
|
||
// Add draft parameter
|
||
form.append('<input type="hidden" name="save_as_draft" value="1">');
|
||
|
||
// Submit form
|
||
form.submit();
|
||
}
|
||
|
||
function initializeTrendChart() {
|
||
const ctx = document.getElementById('qcTrendChart').getContext('2d');
|
||
|
||
// Sample trend data - replace with actual data from backend
|
||
const trendData = {
|
||
labels: {{ trend_dates|safe|default:"[]" }},
|
||
datasets: [{
|
||
label: 'QC Results',
|
||
data: {{ trend_values|safe|default:"[]" }},
|
||
borderColor: 'rgb(75, 192, 192)',
|
||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||
tension: 0.1,
|
||
pointRadius: 4,
|
||
pointHoverRadius: 6
|
||
}]
|
||
};
|
||
|
||
new Chart(ctx, {
|
||
type: 'line',
|
||
data: trendData,
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
display: false
|
||
}
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: false,
|
||
title: {
|
||
display: true,
|
||
text: 'Value'
|
||
}
|
||
},
|
||
x: {
|
||
title: {
|
||
display: true,
|
||
text: 'Date'
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function showWestgardRules() {
|
||
alert('Westgard Rules:\n\n1₂s: 1 control observation exceeds 2s\n1₃s: 1 control observation exceeds 3s\n2₂s: 2 consecutive control observations exceed 2s\n4₁s: 4 consecutive control observations exceed 1s\nR₄s: Range of 4 consecutive controls exceeds 4s\n10x̄: 10 consecutive controls on same side of mean');
|
||
}
|
||
</script>
|
||
{% endblock %}
|
||
|