hospital-management/templates/pharmacy/pharmacy_workflow.html
2025-08-12 13:33:25 +03:00

860 lines
43 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Pharmacy Workflow - Pharmacy{% 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>Pharmacy Workflow Dashboard</h4>
<h6>Real-time workflow management and prescription processing overview</h6>
</div>
<div class="page-btn">
<div class="btn-group">
<button type="button" class="btn btn-primary" onclick="refreshWorkflow()">
<i class="fa fa-sync"></i> Refresh
</button>
<button type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#workflowSettingsModal">
<i class="fa fa-cog"></i> Settings
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Workflow Overview -->
<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_prescriptions|default:24 }}</h3>
</div>
</div>
<div class="dash-widget-info">
<h6 class="text-muted">Pending Prescriptions</h6>
<div class="progress progress-sm">
<div class="progress-bar bg-primary" style="width: {{ workflow_stats.pending_percentage|default:60 }}%"></div>
</div>
</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-clock"></i>
</span>
<div class="dash-count">
<h3>{{ workflow_stats.in_progress|default:18 }}</h3>
</div>
</div>
<div class="dash-widget-info">
<h6 class="text-muted">In Progress</h6>
<div class="progress progress-sm">
<div class="progress-bar bg-warning" style="width: {{ workflow_stats.progress_percentage|default:45 }}%"></div>
</div>
</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-check-circle"></i>
</span>
<div class="dash-count">
<h3>{{ workflow_stats.ready_for_pickup|default:12 }}</h3>
</div>
</div>
<div class="dash-widget-info">
<h6 class="text-muted">Ready for Pickup</h6>
<div class="progress progress-sm">
<div class="progress-bar bg-success" style="width: {{ workflow_stats.ready_percentage|default:30 }}%"></div>
</div>
</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-users"></i>
</span>
<div class="dash-count">
<h3>{{ workflow_stats.active_staff|default:8 }}</h3>
</div>
</div>
<div class="dash-widget-info">
<h6 class="text-muted">Active Staff</h6>
<div class="progress progress-sm">
<div class="progress-bar bg-info" style="width: 80%"></div>
</div>
</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">Prescription Workflow Pipeline</h5>
<div class="card-tools">
<span class="badge bg-info">Last updated: {{ last_updated|date:"H:i" }}</span>
</div>
</div>
<div class="card-body">
<div class="workflow-pipeline">
<div class="row">
<!-- Stage 1: Received -->
<div class="col-md-2">
<div class="workflow-stage">
<div class="stage-header bg-primary text-white">
<h6 class="mb-0">
<i class="fa fa-inbox"></i> Received
<span class="badge bg-light text-dark ms-2">{{ stages.received.count|default:8 }}</span>
</h6>
</div>
<div class="stage-content">
{% for prescription in stages.received.prescriptions %}
<div class="prescription-card mb-2" data-prescription-id="{{ prescription.id }}">
<div class="card-sm">
<div class="card-body p-2">
<div class="d-flex justify-content-between">
<small><strong>{{ prescription.prescription_number }}</strong></small>
<span class="badge bg-{{ prescription.priority_color }}">{{ prescription.priority|slice:":1"|upper }}</span>
</div>
<div class="text-muted small">
{{ prescription.patient.get_full_name|truncatechars:20 }}<br>
{{ prescription.medication.name|truncatechars:15 }}
</div>
<div class="text-muted small">
{{ prescription.time_received|timesince }} ago
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fa fa-check-circle"></i><br>
<small>No prescriptions</small>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Stage 2: Data Entry -->
<div class="col-md-2">
<div class="workflow-stage">
<div class="stage-header bg-info text-white">
<h6 class="mb-0">
<i class="fa fa-keyboard"></i> Data Entry
<span class="badge bg-light text-dark ms-2">{{ stages.data_entry.count|default:6 }}</span>
</h6>
</div>
<div class="stage-content">
{% for prescription in stages.data_entry.prescriptions %}
<div class="prescription-card mb-2" data-prescription-id="{{ prescription.id }}">
<div class="card-sm">
<div class="card-body p-2">
<div class="d-flex justify-content-between">
<small><strong>{{ prescription.prescription_number }}</strong></small>
<span class="badge bg-{{ prescription.priority_color }}">{{ prescription.priority|slice:":1"|upper }}</span>
</div>
<div class="text-muted small">
{{ prescription.patient.get_full_name|truncatechars:20 }}<br>
{{ prescription.medication.name|truncatechars:15 }}
</div>
<div class="text-muted small">
Assigned: {{ prescription.assigned_technician.first_name|default:"Unassigned" }}
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fa fa-check-circle"></i><br>
<small>No prescriptions</small>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Stage 3: Verification -->
<div class="col-md-2">
<div class="workflow-stage">
<div class="stage-header bg-warning text-dark">
<h6 class="mb-0">
<i class="fa fa-clipboard-check"></i> Verification
<span class="badge bg-light text-dark ms-2">{{ stages.verification.count|default:5 }}</span>
</h6>
</div>
<div class="stage-content">
{% for prescription in stages.verification.prescriptions %}
<div class="prescription-card mb-2" data-prescription-id="{{ prescription.id }}">
<div class="card-sm">
<div class="card-body p-2">
<div class="d-flex justify-content-between">
<small><strong>{{ prescription.prescription_number }}</strong></small>
<span class="badge bg-{{ prescription.priority_color }}">{{ prescription.priority|slice:":1"|upper }}</span>
</div>
<div class="text-muted small">
{{ prescription.patient.get_full_name|truncatechars:20 }}<br>
{{ prescription.medication.name|truncatechars:15 }}
</div>
<div class="text-muted small">
Pharmacist: {{ prescription.assigned_pharmacist.first_name|default:"Unassigned" }}
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fa fa-check-circle"></i><br>
<small>No prescriptions</small>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Stage 4: Filling -->
<div class="col-md-2">
<div class="workflow-stage">
<div class="stage-header bg-secondary text-white">
<h6 class="mb-0">
<i class="fa fa-pills"></i> Filling
<span class="badge bg-light text-dark ms-2">{{ stages.filling.count|default:4 }}</span>
</h6>
</div>
<div class="stage-content">
{% for prescription in stages.filling.prescriptions %}
<div class="prescription-card mb-2" data-prescription-id="{{ prescription.id }}">
<div class="card-sm">
<div class="card-body p-2">
<div class="d-flex justify-content-between">
<small><strong>{{ prescription.prescription_number }}</strong></small>
<span class="badge bg-{{ prescription.priority_color }}">{{ prescription.priority|slice:":1"|upper }}</span>
</div>
<div class="text-muted small">
{{ prescription.patient.get_full_name|truncatechars:20 }}<br>
{{ prescription.medication.name|truncatechars:15 }}
</div>
<div class="text-muted small">
Technician: {{ prescription.filling_technician.first_name|default:"Unassigned" }}
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fa fa-check-circle"></i><br>
<small>No prescriptions</small>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Stage 5: Final Check -->
<div class="col-md-2">
<div class="workflow-stage">
<div class="stage-header bg-dark text-white">
<h6 class="mb-0">
<i class="fa fa-search"></i> Final Check
<span class="badge bg-light text-dark ms-2">{{ stages.final_check.count|default:3 }}</span>
</h6>
</div>
<div class="stage-content">
{% for prescription in stages.final_check.prescriptions %}
<div class="prescription-card mb-2" data-prescription-id="{{ prescription.id }}">
<div class="card-sm">
<div class="card-body p-2">
<div class="d-flex justify-content-between">
<small><strong>{{ prescription.prescription_number }}</strong></small>
<span class="badge bg-{{ prescription.priority_color }}">{{ prescription.priority|slice:":1"|upper }}</span>
</div>
<div class="text-muted small">
{{ prescription.patient.get_full_name|truncatechars:20 }}<br>
{{ prescription.medication.name|truncatechars:15 }}
</div>
<div class="text-muted small">
Pharmacist: {{ prescription.checking_pharmacist.first_name|default:"Unassigned" }}
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fa fa-check-circle"></i><br>
<small>No prescriptions</small>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Stage 6: Ready -->
<div class="col-md-2">
<div class="workflow-stage">
<div class="stage-header bg-success text-white">
<h6 class="mb-0">
<i class="fa fa-check-circle"></i> Ready
<span class="badge bg-light text-dark ms-2">{{ stages.ready.count|default:12 }}</span>
</h6>
</div>
<div class="stage-content">
{% for prescription in stages.ready.prescriptions %}
<div class="prescription-card mb-2" data-prescription-id="{{ prescription.id }}">
<div class="card-sm">
<div class="card-body p-2">
<div class="d-flex justify-content-between">
<small><strong>{{ prescription.prescription_number }}</strong></small>
<span class="badge bg-{{ prescription.priority_color }}">{{ prescription.priority|slice:":1"|upper }}</span>
</div>
<div class="text-muted small">
{{ prescription.patient.get_full_name|truncatechars:20 }}<br>
{{ prescription.medication.name|truncatechars:15 }}
</div>
<div class="text-muted small">
Ready: {{ prescription.ready_time|timesince }} ago
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fa fa-check-circle"></i><br>
<small>No prescriptions</small>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Staff Workload -->
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="card-title">Staff Workload Distribution</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Staff Member</th>
<th>Role</th>
<th>Current Tasks</th>
<th>Completed Today</th>
<th>Average Time</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for staff in staff_workload %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar avatar-sm me-2">
<span class="avatar-title bg-{{ staff.status_color }} text-white">
{{ staff.first_name|slice:":1" }}{{ staff.last_name|slice:":1" }}
</span>
</div>
{{ staff.get_full_name }}
</div>
</td>
<td>{{ staff.role|title }}</td>
<td>
<span class="badge bg-primary">{{ staff.current_tasks|default:0 }}</span>
</td>
<td>{{ staff.completed_today|default:0 }}</td>
<td>{{ staff.average_time|default:"--" }} min</td>
<td>
<span class="badge bg-{{ staff.status_color }}">{{ staff.status|title }}</span>
</td>
<td>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="assignTask({{ staff.id }})">
<i class="fa fa-plus"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-info"
onclick="viewWorkload({{ staff.id }})">
<i class="fa fa-eye"></i>
</button>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center text-muted">No staff members on duty</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">Workflow Metrics</h5>
</div>
<div class="card-body">
<div class="metric-item mb-3">
<div class="d-flex justify-content-between">
<span>Average Processing Time</span>
<strong>{{ metrics.avg_processing_time|default:45 }} min</strong>
</div>
<div class="progress progress-sm">
<div class="progress-bar bg-info" style="width: 75%"></div>
</div>
</div>
<div class="metric-item mb-3">
<div class="d-flex justify-content-between">
<span>Throughput Today</span>
<strong>{{ metrics.throughput_today|default:156 }} Rx</strong>
</div>
<div class="progress progress-sm">
<div class="progress-bar bg-success" style="width: 85%"></div>
</div>
</div>
<div class="metric-item mb-3">
<div class="d-flex justify-content-between">
<span>Error Rate</span>
<strong>{{ metrics.error_rate|default:0.2 }}%</strong>
</div>
<div class="progress progress-sm">
<div class="progress-bar bg-success" style="width: 95%"></div>
</div>
</div>
<div class="metric-item mb-3">
<div class="d-flex justify-content-between">
<span>Customer Wait Time</span>
<strong>{{ metrics.wait_time|default:12 }} min</strong>
</div>
<div class="progress progress-sm">
<div class="progress-bar bg-warning" style="width: 60%"></div>
</div>
</div>
<div class="metric-item">
<div class="d-flex justify-content-between">
<span>Staff Utilization</span>
<strong>{{ metrics.staff_utilization|default:78 }}%</strong>
</div>
<div class="progress progress-sm">
<div class="progress-bar bg-primary" style="width: 78%"></div>
</div>
</div>
</div>
</div>
<!-- Alerts and Notifications -->
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title">Workflow Alerts</h5>
</div>
<div class="card-body">
{% for alert in workflow_alerts %}
<div class="alert alert-{{ alert.type }} alert-sm mb-2">
<div class="d-flex justify-content-between">
<span><i class="fa fa-{{ alert.icon }}"></i> {{ alert.message }}</span>
<small>{{ alert.time|timesince }} ago</small>
</div>
</div>
{% empty %}
<div class="text-center text-muted">
<i class="fa fa-check-circle fa-2x mb-2"></i>
<p>No active alerts</p>
</div>
{% endfor %}
</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="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Time</th>
<th>Prescription</th>
<th>Patient</th>
<th>Action</th>
<th>Staff</th>
<th>Stage</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
{% for activity in recent_activities %}
<tr>
<td>{{ activity.timestamp|date:"H:i" }}</td>
<td>{{ activity.prescription_number }}</td>
<td>{{ activity.patient_name }}</td>
<td>{{ activity.action|title }}</td>
<td>{{ activity.staff_member }}</td>
<td>
<span class="badge bg-{{ activity.stage_color }}">{{ activity.stage|title }}</span>
</td>
<td>{{ activity.duration|default:"--" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center text-muted">No recent activity</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Workflow Settings Modal -->
<div class="modal fade" id="workflowSettingsModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Workflow Settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="workflowSettingsForm">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Auto-refresh Interval</label>
<select name="refresh_interval" class="form-select">
<option value="30">30 seconds</option>
<option value="60" selected>1 minute</option>
<option value="120">2 minutes</option>
<option value="300">5 minutes</option>
<option value="0">Manual only</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Priority Display</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="show_priority" id="showPriority" checked>
<label class="form-check-label" for="showPriority">
Show priority indicators
</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Sound Notifications</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="sound_alerts" id="soundAlerts">
<label class="form-check-label" for="soundAlerts">
Enable sound alerts for urgent prescriptions
</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Workflow View</label>
<select name="workflow_view" class="form-select">
<option value="pipeline" selected>Pipeline View</option>
<option value="list">List View</option>
<option value="kanban">Kanban Board</option>
</select>
</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 Settings</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh functionality
let refreshInterval = 60000; // 1 minute default
let refreshTimer;
function startAutoRefresh() {
if (refreshInterval > 0) {
refreshTimer = setInterval(refreshWorkflow, refreshInterval);
}
}
function stopAutoRefresh() {
if (refreshTimer) {
clearInterval(refreshTimer);
}
}
// Start auto-refresh
startAutoRefresh();
// Prescription card click handlers
document.querySelectorAll('.prescription-card').forEach(card => {
card.addEventListener('click', function() {
const prescriptionId = this.getAttribute('data-prescription-id');
showPrescriptionDetails(prescriptionId);
});
});
// Workflow settings form handler
document.getElementById('workflowSettingsForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const newInterval = parseInt(formData.get('refresh_interval')) * 1000;
// Update refresh interval
refreshInterval = newInterval;
stopAutoRefresh();
startAutoRefresh();
// Close modal
bootstrap.Modal.getInstance(document.getElementById('workflowSettingsModal')).hide();
alert('Workflow settings updated successfully!');
});
// Drag and drop functionality for workflow stages
enableDragAndDrop();
});
function refreshWorkflow() {
console.log('Refreshing workflow data...');
// Show refresh indicator
const refreshBtn = document.querySelector('button[onclick="refreshWorkflow()"]');
const originalContent = refreshBtn.innerHTML;
refreshBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Refreshing...';
refreshBtn.disabled = true;
// Simulate API call
setTimeout(() => {
// In real implementation, this would fetch updated data
location.reload();
}, 1000);
}
function assignTask(staffId) {
// Handle task assignment
const taskType = prompt('Select task type:\n1. Data Entry\n2. Verification\n3. Filling\n4. Final Check\n\nEnter number:');
if (taskType && ['1', '2', '3', '4'].includes(taskType)) {
const taskTypes = {
'1': 'Data Entry',
'2': 'Verification',
'3': 'Filling',
'4': 'Final Check'
};
console.log(`Assigning ${taskTypes[taskType]} task to staff ${staffId}`);
alert(`${taskTypes[taskType]} task assigned successfully!`);
}
}
function viewWorkload(staffId) {
// Handle workload viewing
console.log(`Viewing workload for staff ${staffId}`);
alert('Workload details would be displayed here.');
}
function showPrescriptionDetails(prescriptionId) {
// Handle prescription details display
console.log(`Showing details for prescription ${prescriptionId}`);
// In real implementation, this would open a modal or navigate to details page
if (confirm('View prescription details?')) {
window.open(`/pharmacy/prescription/${prescriptionId}/`, '_blank');
}
}
function enableDragAndDrop() {
// Enable drag and drop for prescription cards
const prescriptionCards = document.querySelectorAll('.prescription-card');
const workflowStages = document.querySelectorAll('.workflow-stage .stage-content');
prescriptionCards.forEach(card => {
card.draggable = true;
card.addEventListener('dragstart', function(e) {
e.dataTransfer.setData('text/plain', this.getAttribute('data-prescription-id'));
this.style.opacity = '0.5';
});
card.addEventListener('dragend', function(e) {
this.style.opacity = '1';
});
});
workflowStages.forEach(stage => {
stage.addEventListener('dragover', function(e) {
e.preventDefault();
this.style.backgroundColor = '#f8f9fa';
});
stage.addEventListener('dragleave', function(e) {
this.style.backgroundColor = '';
});
stage.addEventListener('drop', function(e) {
e.preventDefault();
this.style.backgroundColor = '';
const prescriptionId = e.dataTransfer.getData('text/plain');
const newStage = this.closest('.workflow-stage').querySelector('.stage-header h6').textContent.trim().split(' ')[1];
if (confirm(`Move prescription ${prescriptionId} to ${newStage} stage?`)) {
// Handle stage change
console.log(`Moving prescription ${prescriptionId} to ${newStage}`);
// In real implementation, make API call to update prescription stage
alert('Prescription moved successfully!');
refreshWorkflow();
}
});
});
}
// Real-time updates simulation
function simulateRealTimeUpdates() {
setInterval(() => {
// Simulate new prescription arrival
if (Math.random() < 0.1) { // 10% chance every interval
console.log('New prescription received');
// Update received count
const receivedBadge = document.querySelector('.workflow-stage:first-child .badge');
if (receivedBadge) {
const currentCount = parseInt(receivedBadge.textContent);
receivedBadge.textContent = currentCount + 1;
}
}
}, 30000); // Check every 30 seconds
}
// Start real-time updates simulation
simulateRealTimeUpdates();
</script>
<style>
.workflow-pipeline {
min-height: 400px;
}
.workflow-stage {
height: 100%;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
overflow: hidden;
}
.stage-header {
padding: 0.75rem;
border-bottom: 1px solid rgba(255,255,255,0.2);
}
.stage-content {
padding: 0.5rem;
height: 350px;
overflow-y: auto;
}
.prescription-card {
cursor: pointer;
transition: transform 0.2s;
}
.prescription-card:hover {
transform: translateY(-2px);
}
.prescription-card .card-sm {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
transition: border-color 0.2s;
}
.prescription-card:hover .card-sm {
border-color: #007bff;
}
.metric-item {
padding: 0.5rem 0;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-title {
font-size: 0.75rem;
font-weight: bold;
}
.alert-sm {
padding: 0.5rem;
font-size: 0.875rem;
}
@media (max-width: 768px) {
.workflow-pipeline .col-md-2 {
margin-bottom: 1rem;
}
.stage-content {
height: 200px;
}
}
</style>
{% endblock %}