Marwan Alwali 35be20ae4c update
2025-09-06 19:07:14 +03:00

605 lines
22 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Blood Bank Inventory Dashboard{% endblock %}
{% block css %}
<style>
.inventory-card {
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.inventory-card:hover {
transform: translateY(-5px);
}
.blood-group-card {
border-left: 4px solid;
margin-bottom: 15px;
}
.blood-group-o { border-left-color: #dc3545; }
.blood-group-a { border-left-color: #007bff; }
.blood-group-b { border-left-color: #28a745; }
.blood-group-ab { border-left-color: #ffc107; }
.expiry-alert {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
.temperature-normal { color: #28a745; }
.temperature-warning { color: #ffc107; }
.temperature-critical { color: #dc3545; }
.chart-container {
position: relative;
height: 300px;
}
</style>
{% endblock %}
{% block content %}
<!-- BEGIN breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'blood_bank:dashboard' %}">Blood Bank</a></li>
<li class="breadcrumb-item active">Inventory Dashboard</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">Blood Bank Inventory <small>real-time inventory monitoring</small></h1>
<!-- END page-header -->
<!-- BEGIN summary cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6">
<div class="card inventory-card bg-primary text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Total Units</h6>
<h2 class="mb-0">{{ total_units }}</h2>
<small>Available for transfusion</small>
</div>
<div class="align-self-center">
<i class="fa fa-tint fa-3x opacity-75"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card inventory-card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Fresh Units</h6>
<h2 class="mb-0">{{ fresh_units }}</h2>
<small>Less than 7 days old</small>
</div>
<div class="align-self-center">
<i class="fa fa-check-circle fa-3x opacity-75"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card inventory-card bg-warning text-white expiry-alert">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Expiring Soon</h6>
<h2 class="mb-0">{{ expiring_units }}</h2>
<small>Within 3 days</small>
</div>
<div class="align-self-center">
<i class="fa fa-exclamation-triangle fa-3x opacity-75"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card inventory-card bg-danger text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Critical Low</h6>
<h2 class="mb-0">{{ critical_low_count }}</h2>
<small>Below minimum levels</small>
</div>
<div class="align-self-center">
<i class="fa fa-exclamation-circle fa-3x opacity-75"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- END summary cards -->
<!-- BEGIN row -->
<div class="row">
<!-- BEGIN col-8 -->
<div class="col-xl-8">
<!-- BEGIN blood group inventory -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Blood Group Inventory</h4>
<div class="panel-heading-btn">
<button type="button" class="btn btn-info btn-sm" onclick="refreshInventory()">
<i class="fa fa-refresh"></i> Refresh
</button>
</div>
</div>
<div class="panel-body">
<div class="row">
{% for group in blood_groups %}
<div class="col-md-6">
<div class="blood-group-card card blood-group-{{ group.abo_type|lower }}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="card-title mb-1">{{ group.display_name }}</h5>
<div class="row">
<div class="col-6">
<small class="text-muted">Whole Blood</small>
<h6 class="mb-0">{{ group.whole_blood_count }}</h6>
</div>
<div class="col-6">
<small class="text-muted">RBC</small>
<h6 class="mb-0">{{ group.rbc_count }}</h6>
</div>
</div>
<div class="row">
<div class="col-6">
<small class="text-muted">Plasma</small>
<h6 class="mb-0">{{ group.plasma_count }}</h6>
</div>
<div class="col-6">
<small class="text-muted">Platelets</small>
<h6 class="mb-0">{{ group.platelet_count }}</h6>
</div>
</div>
</div>
<div class="text-end">
<h3 class="mb-0">{{ group.total_units }}</h3>
<small class="text-muted">Total Units</small>
{% if group.is_critical_low %}
<br><span class="badge bg-danger">Critical</span>
{% elif group.is_low %}
<br><span class="badge bg-warning">Low</span>
{% else %}
<br><span class="badge bg-success">Good</span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- END blood group inventory -->
<!-- BEGIN inventory trends -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Inventory Trends</h4>
<div class="panel-heading-btn">
<select class="form-select form-select-sm" id="trendPeriod" onchange="updateTrendChart()">
<option value="7">Last 7 Days</option>
<option value="30">Last 30 Days</option>
<option value="90">Last 3 Months</option>
</select>
</div>
</div>
<div class="panel-body">
<div class="chart-container">
<canvas id="inventoryTrendChart"></canvas>
</div>
</div>
</div>
<!-- END inventory trends -->
<!-- BEGIN component distribution -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Component Distribution</h4>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="chart-container">
<canvas id="componentChart"></canvas>
</div>
</div>
<div class="col-md-6">
<div class="chart-container">
<canvas id="bloodGroupChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- END component distribution -->
</div>
<!-- END col-8 -->
<!-- BEGIN col-4 -->
<div class="col-xl-4">
<!-- BEGIN storage locations -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Storage Locations</h4>
</div>
<div class="panel-body">
{% for location in storage_locations %}
<div class="card mb-3">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title mb-1">{{ location.name }}</h6>
<small class="text-muted">{{ location.location_type }}</small>
</div>
<div class="text-end">
<h5 class="mb-0">{{ location.unit_count }}</h5>
<small class="text-muted">Units</small>
</div>
</div>
<div class="mt-2">
<div class="d-flex justify-content-between">
<small>Temperature:</small>
<small class="{% if location.temperature_status == 'normal' %}temperature-normal{% elif location.temperature_status == 'warning' %}temperature-warning{% else %}temperature-critical{% endif %}">
{{ location.current_temperature }}°C
<i class="fa fa-thermometer-half"></i>
</small>
</div>
<div class="progress mt-1" style="height: 5px;">
<div class="progress-bar {% if location.capacity_percentage > 90 %}bg-danger{% elif location.capacity_percentage > 75 %}bg-warning{% else %}bg-success{% endif %}"
style="width: {{ location.capacity_percentage }}%"></div>
</div>
<small class="text-muted">{{ location.capacity_percentage }}% capacity</small>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- END storage locations -->
<!-- BEGIN expiry alerts -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Expiry Alerts</h4>
<div class="panel-heading-btn">
<span class="badge bg-warning">{{ expiring_units }}</span>
</div>
</div>
<div class="panel-body">
{% for unit in expiring_units_list %}
<div class="alert alert-warning d-flex justify-content-between align-items-center">
<div>
<strong>{{ unit.unit_number }}</strong><br>
<small>{{ unit.blood_group.display_name }} - {{ unit.component.get_name_display }}</small>
</div>
<div class="text-end">
<span class="badge bg-danger">{{ unit.days_to_expiry }} days</span><br>
<small class="text-muted">{{ unit.expiry_date|date:"M d" }}</small>
</div>
</div>
{% empty %}
<div class="text-center py-3">
<i class="fa fa-check-circle fa-2x text-success mb-2"></i>
<p class="text-muted mb-0">No units expiring soon</p>
</div>
{% endfor %}
</div>
</div>
<!-- END expiry alerts -->
<!-- BEGIN recent activity -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Recent Activity</h4>
</div>
<div class="panel-body">
<div class="timeline">
{% for activity in recent_activities %}
<div class="timeline-item">
<div class="timeline-time">{{ activity.timestamp|date:"H:i" }}</div>
<div class="timeline-body">
<div class="timeline-content">
<i class="fa {{ activity.icon }} text-{{ activity.color }}"></i>
{{ activity.description }}
</div>
</div>
</div>
{% empty %}
<div class="text-center py-3">
<i class="fa fa-clock fa-2x text-muted mb-2"></i>
<p class="text-muted mb-0">No recent activity</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- END recent activity -->
<!-- BEGIN quick actions -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Quick Actions</h4>
</div>
<div class="panel-body">
<div class="d-grid gap-2">
<a href="{% url 'blood_bank:blood_unit_create' %}" class="btn btn-primary">
<i class="fa fa-plus"></i> Register Blood Unit
</a>
<a href="{% url 'blood_bank:blood_request_list' %}" class="btn btn-info">
<i class="fa fa-clipboard-list"></i> View Requests
</a>
<a href="{% url 'blood_bank:donor_list' %}" class="btn btn-success">
<i class="fa fa-users"></i> Manage Donors
</a>
<button type="button" class="btn btn-warning" onclick="generateInventoryReport()">
<i class="fa fa-file-pdf"></i> Generate Report
</button>
</div>
</div>
</div>
<!-- END quick actions -->
</div>
<!-- END col-4 -->
</div>
<!-- END row -->
{% endblock %}
{% block js %}
<script src="{% static 'plugins/chart.js/dist/chart.js' %}"></script>
<script>
$(document).ready(function() {
initializeCharts();
// Auto-refresh every 5 minutes
setInterval(function() {
if (document.visibilityState === 'visible') {
refreshInventory();
}
}, 300000);
});
function initializeCharts() {
// Inventory Trend Chart
var trendCtx = document.getElementById('inventoryTrendChart').getContext('2d');
var trendChart = new Chart(trendCtx, {
type: 'line',
data: {
labels: {{ trend_labels|safe }},
datasets: [{
label: 'Total Units',
data: {{ trend_data|safe }},
borderColor: '#007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
// Component Distribution Chart
var componentCtx = document.getElementById('componentChart').getContext('2d');
var componentChart = new Chart(componentCtx, {
type: 'doughnut',
data: {
labels: {{ component_labels|safe }},
datasets: [{
data: {{ component_data|safe }},
backgroundColor: [
'#dc3545',
'#007bff',
'#28a745',
'#ffc107',
'#6f42c1'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'By Component'
}
}
}
});
// Blood Group Distribution Chart
var bloodGroupCtx = document.getElementById('bloodGroupChart').getContext('2d');
var bloodGroupChart = new Chart(bloodGroupCtx, {
type: 'doughnut',
data: {
labels: {{ blood_group_labels|safe }},
datasets: [{
data: {{ blood_group_data|safe }},
backgroundColor: [
'#dc3545',
'#007bff',
'#28a745',
'#ffc107'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'By Blood Group'
}
}
}
});
}
function refreshInventory() {
// Show loading indicator
Swal.fire({
title: 'Refreshing Inventory...',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
// Simulate refresh (in real implementation, this would be an AJAX call)
setTimeout(function() {
Swal.close();
location.reload();
}, 2000);
}
function updateTrendChart() {
var period = document.getElementById('trendPeriod').value;
// Here you would make an AJAX call to get new data
// For now, we'll just show a message
Swal.fire({
icon: 'info',
title: 'Updating Chart',
text: `Loading data for last ${period} days...`,
timer: 1500,
showConfirmButton: false
});
}
function generateInventoryReport() {
Swal.fire({
title: 'Generate Inventory Report',
html: `
<div class="text-start">
<div class="mb-3">
<label class="form-label">Report Type</label>
<select class="form-select" id="reportType">
<option value="summary">Inventory Summary</option>
<option value="detailed">Detailed Inventory</option>
<option value="expiry">Expiry Report</option>
<option value="usage">Usage Analysis</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Date Range</label>
<select class="form-select" id="dateRange">
<option value="current">Current Inventory</option>
<option value="week">Last Week</option>
<option value="month">Last Month</option>
<option value="quarter">Last Quarter</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Format</label>
<select class="form-select" id="reportFormat">
<option value="pdf">PDF</option>
<option value="excel">Excel</option>
<option value="csv">CSV</option>
</select>
</div>
</div>
`,
showCancelButton: true,
confirmButtonText: 'Generate Report',
cancelButtonText: 'Cancel',
preConfirm: () => {
const reportType = document.getElementById('reportType').value;
const dateRange = document.getElementById('dateRange').value;
const format = document.getElementById('reportFormat').value;
return { reportType, dateRange, format };
}
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
icon: 'success',
title: 'Report Generated',
text: `${result.value.reportType} report in ${result.value.format} format is being prepared.`,
confirmButtonText: 'OK'
});
}
});
}
// Timeline styles
document.addEventListener('DOMContentLoaded', function() {
var style = document.createElement('style');
style.textContent = `
.timeline {
position: relative;
padding-left: 20px;
}
.timeline::before {
content: '';
position: absolute;
left: 10px;
top: 0;
bottom: 0;
width: 2px;
background: #e9ecef;
}
.timeline-item {
position: relative;
margin-bottom: 15px;
}
.timeline-item::before {
content: '';
position: absolute;
left: -15px;
top: 5px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #007bff;
}
.timeline-time {
font-size: 0.8em;
color: #6c757d;
margin-bottom: 2px;
}
.timeline-content {
font-size: 0.9em;
}
`;
document.head.appendChild(style);
});
</script>
{% endblock %}