This commit is contained in:
Marwan Alwali 2025-09-06 16:22:28 +03:00
parent 610e165e17
commit 09932ffe8a
22 changed files with 4061 additions and 399 deletions

View File

@ -1,5 +1,5 @@
from django import forms
from django.contrib.auth.models import User
from accounts.models import User
from django.utils import timezone
from datetime import timedelta
from patients.models import PatientProfile

View File

@ -8,7 +8,7 @@ urlpatterns = [
path('', views.dashboard, name='dashboard'),
# Donor Management
path('donors/', views.donor_list, name='donor_list'),
path('donors/', views.DonorListView.as_view(), name='donor_list'),
path('donors/<int:donor_id>/', views.donor_detail, name='donor_detail'),
path('donors/create/', views.donor_create, name='donor_create'),
path('donors/<int:donor_id>/update/', views.donor_update, name='donor_update'),

View File

@ -1,3 +1,4 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
@ -10,6 +11,8 @@ from django.contrib.auth.models import User
from datetime import timedelta
import json
from django.views.generic import ListView
from .models import (
BloodGroup, Donor, BloodComponent, BloodUnit, BloodTest, CrossMatch,
BloodRequest, BloodIssue, Transfusion, AdverseReaction, InventoryLocation,
@ -61,45 +64,50 @@ def dashboard(request):
# Donor Management Views
@login_required
def donor_list(request):
"""List all donors with filtering and search"""
donors = Donor.objects.select_related('blood_group').order_by('-registration_date')
class DonorListView(LoginRequiredMixin, ListView):
model = Donor
template_name = 'blood_bank/donors/donor_list.html'
context_object_name = 'page_obj'
paginate_by = 25
# Search functionality
search_query = request.GET.get('search')
if search_query:
donors = donors.filter(
Q(donor_id__icontains=search_query) |
Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) |
Q(phone__icontains=search_query)
)
def get_queryset(self):
queryset = Donor.objects.select_related('blood_group').order_by('-registration_date')
# Filter by status
status_filter = request.GET.get('status')
if status_filter:
donors = donors.filter(status=status_filter)
# Search functionality
search_query = self.request.GET.get('search')
if search_query:
queryset = queryset.filter(
Q(donor_id__icontains=search_query) |
Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) |
Q(phone__icontains=search_query)
)
# Filter by blood group
blood_group_filter = request.GET.get('blood_group')
if blood_group_filter:
donors = donors.filter(blood_group_id=blood_group_filter)
# Filter by status
status_filter = self.request.GET.get('status')
if status_filter:
queryset = queryset.filter(status=status_filter)
paginator = Paginator(donors, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
# Filter by blood group
blood_group_filter = self.request.GET.get('blood_group')
if blood_group_filter:
queryset = queryset.filter(blood_group_id=blood_group_filter)
context = {
'page_obj': page_obj,
'blood_groups': BloodGroup.objects.all(),
'status_choices': Donor.STATUS_CHOICES,
'search_query': search_query,
'status_filter': status_filter,
'blood_group_filter': blood_group_filter,
}
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'blood_groups': BloodGroup.objects.all(),
'status_choices': Donor.STATUS_CHOICES,
'search_query': self.request.GET.get('search'),
'status_filter': self.request.GET.get('status'),
'blood_group_filter': self.request.GET.get('blood_group'),
})
return context
return render(request, 'blood_bank/donors/donor_list.html', context)
@login_required
@ -553,7 +561,7 @@ def inventory_overview(request):
'locations': locations,
}
return render(request, 'blood_bank/inventory/inventory_overview.html', context)
return render(request, 'blood_bank/inventory/inventory_dashboard.html', context)
# Quality Control Views

View File

@ -28,10 +28,6 @@ from .forms import (
)
# ============================================================================
# DASHBOARD AND OVERVIEW VIEWS
# ============================================================================
class InventoryDashboardView(LoginRequiredMixin, TemplateView):
"""
Main inventory dashboard with comprehensive statistics and recent activity.
@ -52,11 +48,11 @@ class InventoryDashboardView(LoginRequiredMixin, TemplateView):
).count()
# Stock statistics
# low_stock_items = InventoryStock.objects.filter(
# tenant=user.tenant,
# quantity_available__lte=F('min_stock_level')
# ).count()
# context['low_stock_items'] = low_stock_items
low_stock_items = InventoryStock.objects.filter(
inventory_item__tenant=user.tenant,
quantity_available__lte=F('inventory_item__min_stock_level')
).count()
context['low_stock_items'] = low_stock_items
expired_items = InventoryStock.objects.filter(
inventory_item__tenant=user.tenant,
@ -84,22 +80,18 @@ class InventoryDashboardView(LoginRequiredMixin, TemplateView):
tenant=user.tenant
).order_by('-created_at')[:10]
# context['low_stock_alerts'] = InventoryStock.objects.filter(
# inventory_item__tenant=user.tenant,
# quantity__lte=F('minimum_stock_level')
# ).select_related('item', 'location').order_by('quantity')[:10]
context['low_stock_alerts'] = InventoryStock.objects.filter(
inventory_item__tenant=user.tenant,
quantity_available__lte=F('inventory_item__min_stock_level')
).select_related('inventory_item', 'location').order_by('quantity_available')[:10]
# context['recent_stock_movements'] = InventoryStock.objects.filter(
# tenant=user.tenant
# ).order_by('-updated_at')[:10]
context['recent_stock_movements'] = InventoryStock.objects.filter(
inventory_item__tenant=user.tenant
).order_by('-updated_at')[:10]
return context
# ============================================================================
# SUPPLIER VIEWS (FULL CRUD - Master Data)
# ============================================================================
class SupplierListView(LoginRequiredMixin, ListView):
"""
List all suppliers with search and filtering capabilities.
@ -286,10 +278,6 @@ class SupplierDeleteView(LoginRequiredMixin, DeleteView):
return redirect(self.success_url)
# ============================================================================
# INVENTORY LOCATION VIEWS (FULL CRUD - Master Data)
# ============================================================================
class InventoryLocationListView(LoginRequiredMixin, ListView):
"""
List all inventory locations.
@ -442,10 +430,6 @@ class InventoryLocationDeleteView(LoginRequiredMixin, DeleteView):
return response
# ============================================================================
# INVENTORY ITEM VIEWS (FULL CRUD - Master Data)
# ============================================================================
class InventoryItemListView(LoginRequiredMixin, ListView):
"""
List all inventory items with search and filtering.
@ -609,10 +593,6 @@ class InventoryItemDeleteView(LoginRequiredMixin, DeleteView):
return redirect(self.success_url)
# ============================================================================
# INVENTORY STOCK VIEWS (LIMITED CRUD - Operational Data)
# ============================================================================
class InventoryStockListView(LoginRequiredMixin, ListView):
"""
List all inventory stock with filtering and search.
@ -736,10 +716,6 @@ class InventoryStockUpdateView(LoginRequiredMixin, UpdateView):
return response
# ============================================================================
# PURCHASE ORDER VIEWS (RESTRICTED CRUD - Operational Data)
# ============================================================================
class PurchaseOrderListView(LoginRequiredMixin, ListView):
"""
List all purchase orders with filtering.
@ -872,10 +848,6 @@ class PurchaseOrderUpdateView(LoginRequiredMixin, UpdateView):
return response
# ============================================================================
# HTMX AND AJAX VIEWS
# ============================================================================
@login_required
def inventory_stats(request):
"""
@ -898,6 +870,10 @@ def inventory_stats(request):
tenant=user.tenant,
status__in=['PENDING', 'APPROVED', 'ORDERED']
).count(),
'recent_stock_movements': InventoryStock.objects.filter(
inventory_item__tenant=user.tenant,
movement_date__gte=timezone.now().date() - timedelta(days=30)
)
}
return render(request, 'inventory/partials/inventory_stats.html', stats)
@ -919,10 +895,6 @@ def stock_search(request):
})
# ============================================================================
# ACTION VIEWS FOR WORKFLOW OPERATIONS
# ============================================================================
@login_required
def adjust_stock(request, stock_id):
"""

File diff suppressed because it is too large Load Diff

BIN
templates/.DS_Store vendored

Binary file not shown.

View File

@ -108,39 +108,7 @@
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Appointment pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}">Previous</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% include 'partial/pagination.html' %}
{% endif %}
{% endblock %}

View File

@ -94,14 +94,12 @@
<div class="btn-group btn-group-sm" role="group">
<!-- Check In Button -->
{% if appointment.status == 'SCHEDULED' or appointment.status == 'CONFIRMED' %}
<button type="button"
<a type="button"
class="btn btn-outline-success"
title="Check In Patient"
hx-post="{% url 'appointments:check_in_patient' appointment.id %}"
hx-target="#appointment-list"
hx-swap="outerHTML">
href="{% url 'appointments:check_in_patient' appointment.id %}">
<i class="fas fa-check"></i>
</button>
</a>
{% endif %}
<!-- Telemedicine Join Button -->

View File

@ -27,11 +27,12 @@
</head>
<body>
<!-- Your page content -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js"></script>
</body>
<script src="{% static 'plugins/apexcharts/dist/apexcharts.min.js' %}"></script>
<script src="{% static 'plugins/chart.js/dist/chart.js' %}"></script>
{# <script src="{% static 'plugins/apexcharts/dist/apexcharts.min.js' %}"></script>#}
<!-- HTMX -->
<script src="{% static 'js/htmx.min.js' %}"></script>
<!-- ================== END core-css ================== -->

Binary file not shown.

View File

@ -3,9 +3,9 @@
{% block title %}Blood Bank Dashboard{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
{% block css %}
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
@ -346,11 +346,11 @@
<!-- END row -->
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
{% block js %}
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {

View File

@ -3,9 +3,9 @@
{% block title %}Donor Management{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
{% block css %}
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
@ -182,68 +182,24 @@
<!-- END table -->
<!-- BEGIN pagination -->
{% if page_obj.has_other_pages %}
<nav aria-label="Donor pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if search_query %}&search={{ search_query }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if blood_group_filter %}&blood_group={{ blood_group_filter }}{% endif %}">
<i class="fa fa-angle-double-left"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if blood_group_filter %}&blood_group={{ blood_group_filter }}{% endif %}">
<i class="fa fa-angle-left"></i>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&search={{ search_query }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if blood_group_filter %}&blood_group={{ blood_group_filter }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if blood_group_filter %}&blood_group={{ blood_group_filter }}{% endif %}">
<i class="fa fa-angle-right"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&search={{ search_query }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if blood_group_filter %}&blood_group={{ blood_group_filter }}{% endif %}">
<i class="fa fa-angle-double-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
<!-- END pagination -->
<!-- BEGIN summary -->
<div class="row mt-3">
<div class="col-md-6">
<p class="text-muted">
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} donors
</p>
</div>
<div class="col-md-6 text-end">
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.print()">
<i class="fa fa-print"></i> Print
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="exportToCSV()">
<i class="fa fa-download"></i> Export
</button>
</div>
</div>
{# <div class="col-md-6 text-end">#}
{# <div class="btn-group" role="group">#}
{# <button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.print()">#}
{# <i class="fa fa-print"></i> Print#}
{# </button>#}
{# <button type="button" class="btn btn-outline-secondary btn-sm" onclick="exportToCSV()">#}
{# <i class="fa fa-download"></i> Export#}
{# </button>#}
{# </div>#}
{# </div>#}
</div>
<!-- END summary -->
</div>
@ -251,18 +207,18 @@
<!-- END panel -->
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
{% block js %}
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize DataTable for enhanced functionality
$('#donorTable').DataTable({
responsive: true,
pageLength: 25,
{#pageLength: 25,#}
order: [[0, 'desc']],
columnDefs: [
{ orderable: false, targets: [9] } // Actions column

View File

@ -3,8 +3,8 @@
{% block title %}Blood Bank Inventory Dashboard{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/chart.js/dist/Chart.min.css' %}" rel="stylesheet" />
{% block css %}
<link href="{% static 'plugins/chart.js/dist/Chart.min.css' %}" rel="stylesheet" />
<style>
.inventory-card {
border-radius: 10px;
@ -368,7 +368,7 @@
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/chart.js/dist/Chart.min.js' %}"></script>
<script src="{% static 'plugins/chart.js/dist/chart.js' %}"></script>
<script>
$(document).ready(function() {

View File

@ -4,9 +4,9 @@
{% block title %}Blood Unit Management{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
@ -250,62 +250,20 @@
{% endfor %}
</tbody>
</table>
</div>
<!-- END table -->
<!-- BEGIN pagination -->
{% if page_obj.has_other_pages %}
<nav aria-label="Blood unit pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">
<i class="fa fa-angle-double-left"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
<i class="fa fa-angle-left"></i>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">
<i class="fa fa-angle-right"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">
<i class="fa fa-angle-double-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
<!-- END pagination -->
<!-- BEGIN summary -->
<div class="row mt-3">
<div class="col-md-6">
<p class="text-muted">
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} blood units
</p>
</div>
<div class="col-md-6 text-end">
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="window.print()">

View File

@ -335,7 +335,7 @@
{% block js %}
<!-- DataTables JS -->
<script src="{% static 'plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>

View File

@ -45,141 +45,58 @@
</div>
<!-- Key Performance Indicators -->
<div class="row mb-4" hx-get="{% url 'inventory:inventory_stats' %}" hx-trigger="load, every 30s">
<div class="row mb-4" hx-get="{% url 'inventory:inventory_stats' %}">
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card stat-card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Total Items</h6>
<h3 class="mb-0 text-primary">{{ total_items }}</h3>
<small class="text-muted">{{ active_items }} active</small>
</div>
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-boxes fa-lg text-white"></i>
<h4 class="card-title">{{ total_items }}</h4>
<p class="card-text">Total Items</p>
</div>
<i class="fas fa-boxes fa-2x opacity-75"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card stat-card bg-warning">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Locations</h6>
<h3 class="mb-0 text-success">{{ total_locations }}</h3>
<small class="text-muted">Storage areas</small>
</div>
<div class="bg-success bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-map-marker-alt fa-lg text-white"></i>
<h4 class="card-title">{{ total_locations }}</h4>
<p class="card-text">Locations</p>
</div>
<i class="fas fa-map-marker-alt fa-2x opacity-75"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card stat-card bg-primary text-white">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Suppliers</h6>
<h3 class="mb-0 text-info">{{ total_suppliers }}</h3>
<small class="text-muted">Active partners</small>
</div>
<div class="bg-info bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-truck fa-lg text-white"></i>
<h4 class="card-title">{{ total_suppliers }}</h4>
<p class="card-text">Suppliers</p>
</div>
<i class="fas fa-truck fa-2x opacity-75"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card stat-card bg-danger text-white">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Total Value</h6>
<h3 class="mb-0 text-warning">${{ total_inventory_value|floatformat:0 }}</h3>
<small class="text-muted">Inventory worth</small>
</div>
<div class="bg-warning bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-dollar-sign fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Alert Cards -->
<div class="row mb-4">
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100 {% if low_stock_items > 0 %}border-warning{% endif %}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Low Stock</h6>
<h3 class="mb-0 {% if low_stock_items > 0 %}text-warning{% else %}text-success{% endif %}">{{ low_stock_items }}</h3>
<small class="text-muted">Items below minimum</small>
</div>
<div class="bg-{% if low_stock_items > 0 %}warning{% else %}success{% endif %} bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-exclamation-triangle fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100 {% if expired_items > 0 %}border-danger{% endif %}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Expired</h6>
<h3 class="mb-0 {% if expired_items > 0 %}text-danger{% else %}text-success{% endif %}">{{ expired_items }}</h3>
<small class="text-muted">Past expiry date</small>
</div>
<div class="bg-{% if expired_items > 0 %}danger{% else %}success{% endif %} bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-calendar-times fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100 {% if expiring_soon_items > 0 %}border-warning{% endif %}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Expiring Soon</h6>
<h3 class="mb-0 {% if expiring_soon_items > 0 %}text-warning{% else %}text-success{% endif %}">{{ expiring_soon_items }}</h3>
<small class="text-muted">Within 30 days</small>
</div>
<div class="bg-{% if expiring_soon_items > 0 %}warning{% else %}success{% endif %} bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-clock fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Active Orders</h6>
<h3 class="mb-0 text-primary">{{ active_orders }}</h3>
<small class="text-muted">In progress</small>
</div>
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-shopping-cart fa-lg text-white"></i>
<h4 class="card-title"><span class="symbol">&#xea;</span>{{ total_inventory_value|floatformat:'2g' }}</h4>
<p class="card-text">Total Value</p>
</div>
<i class="fas fa-dollar-sign fa-2x opacity-75"></i>
</div>
</div>
</div>
@ -195,9 +112,9 @@
<div class="panel-heading-btn">
<a href="{% url 'inventory:purchase_order_list' %}" class="btn btn-outline-primary btn-xs"><i class="fas fa-list me-1"></i>View All</a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>#}
</div>
</div>
<div class="panel-body">
@ -216,20 +133,20 @@
<tbody>
{% for order in recent_orders %}
<tr>
<td>
<a href="{% url 'inventory:purchase_order_detail' order.pk %}"
<td class="fs-12px">
<a href="{% url 'inventory:purchase_order_detail' order.pk %}"
class="text-decoration-none fw-semibold">
{{ order.order_number }}
{{ order.po_number }}
</a>
</td>
<td>{{ order.supplier.name|truncatechars:20 }}</td>
<td>
<td class="fs-12px">{{ order.supplier.name }}</td>
<td class="fs-12px">
<span class="badge bg-{% if order.status == 'RECEIVED' %}success{% elif order.status == 'APPROVED' %}info{% elif order.status == 'ORDERED' %}warning{% else %}secondary{% endif %}">
{{ order.get_status_display }}
</span>
</td>
<td><span class="symbol">&#xea;</span>{{ order.total_amount|floatformat:'2g' }}</td>
<td>{{ order.order_date|date:" Y M d" }}</td>
<td class="fs-12px"><span class="symbol">&#xea;</span>{{ order.total_amount|floatformat:'2g' }}</td>
<td class="fs-12px">{{ order.order_date|date:" Y M d" }}</td>
</tr>
{% endfor %}
</tbody>
@ -257,9 +174,9 @@
<div class="panel-heading-btn">
<a href="{% url 'inventory:stock_list' %}?stock_status=low" class="btn btn-outline-warning btn-xs"><i class="fas fa-list me-1"></i>View All</a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>#}
</div>
</div>
<div class="panel-body">
@ -308,7 +225,7 @@
<div class="panel-body">
{% if recent_stock_movements %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th>Item</th>
@ -320,19 +237,19 @@
<tbody>
{% for stock in recent_stock_movements %}
<tr>
<td>
<td class="fs-12px">
<a href="{% url 'inventory:stock_detail' stock.pk %}"
class="text-decoration-none">
{{ stock.item.item_name|truncatechars:25 }}
{{ stock.inventory_item.item_name }}
</a>
</td>
<td>{{ stock.location.location_name|truncatechars:15 }}</td>
<td>
<span class="{% if stock.quantity <= stock.minimum_stock_level %}text-warning{% else %}text-success{% endif %} fw-semibold">
{{ stock.quantity }}
<td class="fs-12px">{{ stock.location.name }}</td>
<td class="fs-12px">
<span class="{% if stock.quantity_available <= stock.inventory_item.min_stock_level %}text-warning{% else %}text-success{% endif %} fw-semibold">
{{ stock.quantity_available }}
</span>
</td>
<td>{{ stock.updated_at|date:"M d, H:i" }}</td>
<td class="fs-12px">{{ stock.updated_at|date:"M d, H:i" }}</td>
</tr>
{% endfor %}
</tbody>
@ -415,9 +332,9 @@
</h4>
<div class="panel-heading-btn">
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>#}
</div>
</div>
<div class="panel-body">
@ -434,9 +351,9 @@
</h4>
<div class="panel-heading-btn">
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>#}
{# <a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>#}
</div>
</div>
<div class="panel-body">
@ -446,6 +363,7 @@
</div>
</div>
</div>
<script src="{% static 'plugins/chart.js/dist/chart.umd.js' %}"></script>
<script>
// Dashboard functionality
document.addEventListener('DOMContentLoaded', function() {
@ -453,7 +371,7 @@ document.addEventListener('DOMContentLoaded', function() {
initializeCharts();
// Auto-refresh dashboard every 5 minutes
setInterval(refreshDashboard, 300000);
{#setInterval(refreshDashboard, 300000);#}
});
function refreshDashboard() {
@ -472,7 +390,7 @@ function refreshDashboard() {
setTimeout(() => {
refreshBtn.innerHTML = originalText;
refreshBtn.disabled = false;
}, 2000);
}, 300000);
}
function initializeCharts() {
@ -486,7 +404,7 @@ function initializeCharts() {
datasets: [{
data: [16, 12, 14, 10, 18, 20, 4],
backgroundColor: [
'#0d6efd', '#198754', '#ffc107', '#dc3545',
'#0d6efd', '#198754', '#ffc107', '#dc3545',
'#6f42c1', '#fd7e14', '#20c997', '#6c757d'
]
}]
@ -502,7 +420,7 @@ function initializeCharts() {
}
});
}
// Stock Status Chart
const stockCtx = document.getElementById('stockStatusChart');
if (stockCtx) {

View File

@ -3,10 +3,10 @@
{% block title %}Insurance Claims Management{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
{% block css %}
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<style>
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@ -432,12 +432,12 @@
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
{% block js %}
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {

View File

@ -4,9 +4,9 @@
{% block title %}Emergency Contacts - Patient Management{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
@ -292,11 +292,11 @@
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
var table;