570 lines
26 KiB
HTML
570 lines
26 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Lab Workflow - Laboratory{% 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>Laboratory Workflow Management</h4>
|
|
<h6>Monitor and manage laboratory workflow processes</h6>
|
|
</div>
|
|
<div class="page-btn">
|
|
<a href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#workflowConfigModal">
|
|
<i class="fa fa-cog"></i> Configure Workflow
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workflow Overview Cards -->
|
|
<div class="row">
|
|
<div class="col-xl-3 col-sm-6 col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-primary border-primary">
|
|
<i class="fa fa-inbox"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ workflow_stats.pending_orders|default:0 }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">Pending Orders</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-sm-6 col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-warning border-warning">
|
|
<i class="fa fa-flask"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ workflow_stats.in_process|default:0 }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">In Process</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-sm-6 col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-info border-info">
|
|
<i class="fa fa-check-circle"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ workflow_stats.ready_for_review|default:0 }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">Ready for Review</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-sm-6 col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="dash-widget-header">
|
|
<span class="dash-widget-icon text-success border-success">
|
|
<i class="fa fa-file-text"></i>
|
|
</span>
|
|
<div class="dash-count">
|
|
<h3>{{ workflow_stats.completed_today|default:0 }}</h3>
|
|
</div>
|
|
</div>
|
|
<div class="dash-widget-info">
|
|
<h6 class="text-muted">Completed Today</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workflow Stages -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">Workflow Stages</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="workflow-container">
|
|
<div class="row">
|
|
<!-- Order Received -->
|
|
<div class="col-md-2">
|
|
<div class="workflow-stage">
|
|
<div class="stage-header bg-primary text-white">
|
|
<i class="fa fa-inbox"></i>
|
|
<h6>Order Received</h6>
|
|
<span class="badge bg-light text-dark">{{ stage_counts.order_received|default:0 }}</span>
|
|
</div>
|
|
<div class="stage-content">
|
|
{% for item in order_received_items %}
|
|
<div class="workflow-item" draggable="true" data-id="{{ item.id }}">
|
|
<div class="item-header">
|
|
<strong>{{ item.order_id }}</strong>
|
|
<span class="badge bg-{{ item.priority_color }}">{{ item.priority }}</span>
|
|
</div>
|
|
<div class="item-details">
|
|
<small>{{ item.patient_name }}</small><br>
|
|
<small>{{ item.test_type }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Specimen Collection -->
|
|
<div class="col-md-2">
|
|
<div class="workflow-stage">
|
|
<div class="stage-header bg-warning text-white">
|
|
<i class="fa fa-vial"></i>
|
|
<h6>Collection</h6>
|
|
<span class="badge bg-light text-dark">{{ stage_counts.collection|default:0 }}</span>
|
|
</div>
|
|
<div class="stage-content" data-stage="collection">
|
|
{% for item in collection_items %}
|
|
<div class="workflow-item" draggable="true" data-id="{{ item.id }}">
|
|
<div class="item-header">
|
|
<strong>{{ item.order_id }}</strong>
|
|
<span class="badge bg-{{ item.priority_color }}">{{ item.priority }}</span>
|
|
</div>
|
|
<div class="item-details">
|
|
<small>{{ item.patient_name }}</small><br>
|
|
<small>{{ item.test_type }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Processing -->
|
|
<div class="col-md-2">
|
|
<div class="workflow-stage">
|
|
<div class="stage-header bg-info text-white">
|
|
<i class="fa fa-cogs"></i>
|
|
<h6>Processing</h6>
|
|
<span class="badge bg-light text-dark">{{ stage_counts.processing|default:0 }}</span>
|
|
</div>
|
|
<div class="stage-content" data-stage="processing">
|
|
{% for item in processing_items %}
|
|
<div class="workflow-item" draggable="true" data-id="{{ item.id }}">
|
|
<div class="item-header">
|
|
<strong>{{ item.order_id }}</strong>
|
|
<span class="badge bg-{{ item.priority_color }}">{{ item.priority }}</span>
|
|
</div>
|
|
<div class="item-details">
|
|
<small>{{ item.patient_name }}</small><br>
|
|
<small>{{ item.test_type }}</small>
|
|
<div class="progress mt-1" style="height: 4px;">
|
|
<div class="progress-bar" style="width: {{ item.progress }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quality Control -->
|
|
<div class="col-md-2">
|
|
<div class="workflow-stage">
|
|
<div class="stage-header bg-secondary text-white">
|
|
<i class="fa fa-shield-alt"></i>
|
|
<h6>Quality Control</h6>
|
|
<span class="badge bg-light text-dark">{{ stage_counts.qc|default:0 }}</span>
|
|
</div>
|
|
<div class="stage-content" data-stage="qc">
|
|
{% for item in qc_items %}
|
|
<div class="workflow-item" draggable="true" data-id="{{ item.id }}">
|
|
<div class="item-header">
|
|
<strong>{{ item.order_id }}</strong>
|
|
<span class="badge bg-{{ item.priority_color }}">{{ item.priority }}</span>
|
|
</div>
|
|
<div class="item-details">
|
|
<small>{{ item.patient_name }}</small><br>
|
|
<small>{{ item.test_type }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Review -->
|
|
<div class="col-md-2">
|
|
<div class="workflow-stage">
|
|
<div class="stage-header bg-dark text-white">
|
|
<i class="fa fa-eye"></i>
|
|
<h6>Review</h6>
|
|
<span class="badge bg-light text-dark">{{ stage_counts.review|default:0 }}</span>
|
|
</div>
|
|
<div class="stage-content" data-stage="review">
|
|
{% for item in review_items %}
|
|
<div class="workflow-item" draggable="true" data-id="{{ item.id }}">
|
|
<div class="item-header">
|
|
<strong>{{ item.order_id }}</strong>
|
|
<span class="badge bg-{{ item.priority_color }}">{{ item.priority }}</span>
|
|
</div>
|
|
<div class="item-details">
|
|
<small>{{ item.patient_name }}</small><br>
|
|
<small>{{ item.test_type }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Completed -->
|
|
<div class="col-md-2">
|
|
<div class="workflow-stage">
|
|
<div class="stage-header bg-success text-white">
|
|
<i class="fa fa-check"></i>
|
|
<h6>Completed</h6>
|
|
<span class="badge bg-light text-dark">{{ stage_counts.completed|default:0 }}</span>
|
|
</div>
|
|
<div class="stage-content" data-stage="completed">
|
|
{% for item in completed_items %}
|
|
<div class="workflow-item" draggable="true" data-id="{{ item.id }}">
|
|
<div class="item-header">
|
|
<strong>{{ item.order_id }}</strong>
|
|
<span class="badge bg-success">Done</span>
|
|
</div>
|
|
<div class="item-details">
|
|
<small>{{ item.patient_name }}</small><br>
|
|
<small>{{ item.test_type }}</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workflow Analytics -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">Workflow Performance</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="workflowChart" height="200"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">Turnaround Time</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="turnaroundChart" height="200"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title">Recent Workflow Activity</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="activity-timeline">
|
|
{% for activity in recent_activities %}
|
|
<div class="activity-item">
|
|
<div class="activity-icon bg-{{ activity.type_color }}">
|
|
<i class="fa fa-{{ activity.icon }}"></i>
|
|
</div>
|
|
<div class="activity-content">
|
|
<h6>{{ activity.title }}</h6>
|
|
<p>{{ activity.description }}</p>
|
|
<small class="text-muted">{{ activity.timestamp|timesince }} ago by {{ activity.user }}</small>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center text-muted">
|
|
<i class="fa fa-clock fa-2x mb-3"></i>
|
|
<p>No recent activity</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workflow Configuration Modal -->
|
|
<div class="modal fade" id="workflowConfigModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Workflow Configuration</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<div class="modal-body">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h6>Stage Configuration</h6>
|
|
<div class="table-responsive">
|
|
<table class="table table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>Stage</th>
|
|
<th>Auto-Advance</th>
|
|
<th>SLA (Hours)</th>
|
|
<th>Required Role</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Order Received</td>
|
|
<td><input type="checkbox" name="auto_advance_order" class="form-check-input"></td>
|
|
<td><input type="number" name="sla_order" class="form-control form-control-sm" value="1"></td>
|
|
<td>
|
|
<select name="role_order" class="form-select form-select-sm">
|
|
<option>Lab Clerk</option>
|
|
<option>Lab Technician</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Collection</td>
|
|
<td><input type="checkbox" name="auto_advance_collection" class="form-check-input"></td>
|
|
<td><input type="number" name="sla_collection" class="form-control form-control-sm" value="2"></td>
|
|
<td>
|
|
<select name="role_collection" class="form-select form-select-sm">
|
|
<option>Phlebotomist</option>
|
|
<option>Nurse</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Processing</td>
|
|
<td><input type="checkbox" name="auto_advance_processing" class="form-check-input"></td>
|
|
<td><input type="number" name="sla_processing" class="form-control form-control-sm" value="4"></td>
|
|
<td>
|
|
<select name="role_processing" class="form-select form-select-sm">
|
|
<option>Lab Technician</option>
|
|
<option>Medical Technologist</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 mt-3">
|
|
<h6>Notification Settings</h6>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="notify_sla_breach" id="notifySLA">
|
|
<label class="form-check-label" for="notifySLA">
|
|
Notify on SLA breach
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="notify_stage_change" id="notifyStage">
|
|
<label class="form-check-label" for="notifyStage">
|
|
Notify on stage changes
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Save Configuration</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.workflow-container {
|
|
min-height: 500px;
|
|
}
|
|
|
|
.workflow-stage {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
height: 400px;
|
|
}
|
|
|
|
.stage-header {
|
|
padding: 10px;
|
|
border-radius: 8px 8px 0 0;
|
|
text-align: center;
|
|
position: relative;
|
|
}
|
|
|
|
.stage-content {
|
|
padding: 10px;
|
|
height: 340px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.workflow-item {
|
|
background: #f8f9fa;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
margin-bottom: 8px;
|
|
cursor: move;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.workflow-item:hover {
|
|
background: #e9ecef;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.workflow-item.dragging {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.stage-content.drag-over {
|
|
background: #e3f2fd;
|
|
border: 2px dashed #2196f3;
|
|
}
|
|
|
|
.activity-timeline {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.activity-item {
|
|
display: flex;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.activity-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 15px;
|
|
color: white;
|
|
}
|
|
|
|
.activity-content {
|
|
flex: 1;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Drag and Drop functionality
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const workflowItems = document.querySelectorAll('.workflow-item');
|
|
const stageContents = document.querySelectorAll('.stage-content');
|
|
|
|
workflowItems.forEach(item => {
|
|
item.addEventListener('dragstart', function(e) {
|
|
e.dataTransfer.setData('text/plain', this.dataset.id);
|
|
this.classList.add('dragging');
|
|
});
|
|
|
|
item.addEventListener('dragend', function() {
|
|
this.classList.remove('dragging');
|
|
});
|
|
});
|
|
|
|
stageContents.forEach(stage => {
|
|
stage.addEventListener('dragover', function(e) {
|
|
e.preventDefault();
|
|
this.classList.add('drag-over');
|
|
});
|
|
|
|
stage.addEventListener('dragleave', function() {
|
|
this.classList.remove('drag-over');
|
|
});
|
|
|
|
stage.addEventListener('drop', function(e) {
|
|
e.preventDefault();
|
|
this.classList.remove('drag-over');
|
|
const itemId = e.dataTransfer.getData('text/plain');
|
|
const newStage = this.dataset.stage;
|
|
|
|
// Here you would make an AJAX call to update the item's stage
|
|
console.log(`Moving item ${itemId} to stage ${newStage}`);
|
|
});
|
|
});
|
|
});
|
|
|
|
// Charts
|
|
const workflowCtx = document.getElementById('workflowChart').getContext('2d');
|
|
new Chart(workflowCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['Pending', 'In Process', 'Review', 'Completed'],
|
|
datasets: [{
|
|
data: [{{ stage_counts.pending|default:0 }}, {{ stage_counts.processing|default:0 }}, {{ stage_counts.review|default:0 }}, {{ stage_counts.completed|default:0 }}],
|
|
backgroundColor: ['#ffc107', '#17a2b8', '#6c757d', '#28a745']
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
|
|
const turnaroundCtx = document.getElementById('turnaroundChart').getContext('2d');
|
|
new Chart(turnaroundCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
|
datasets: [{
|
|
label: 'Average TAT (hours)',
|
|
data: [4.2, 3.8, 4.1, 3.9, 4.3, 5.1, 4.8],
|
|
borderColor: '#007bff',
|
|
tension: 0.1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|