743 lines
33 KiB
HTML
743 lines
33 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Pharmacy Inventory - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
<i class="fas fa-boxes me-2"></i>Pharmacy Inventory
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'pharmacy:dashboard' %}">Pharmacy</a></li>
|
|
<li class="breadcrumb-item active">Inventory</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-download me-2"></i>Export
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="?export=csv"><i class="fas fa-file-csv me-2"></i>CSV</a></li>
|
|
<li><a class="dropdown-item" href="?export=excel"><i class="fas fa-file-excel me-2"></i>Excel</a></li>
|
|
<li><a class="dropdown-item" href="?export=pdf"><i class="fas fa-file-pdf me-2"></i>PDF</a></li>
|
|
</ul>
|
|
<a href="{% url 'pharmacy:inventory_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Add Item
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Inventory Statistics -->
|
|
<div class="row mb-4">
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
|
<div class="card-body text-white">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h3 class="mb-1 fw-bold">{{ total_items|default:0 }}</h3>
|
|
<p class="mb-0 opacity-75">Total Items</p>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-boxes fa-2x"></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" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
|
<div class="card-body text-white">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h3 class="mb-1 fw-bold">{{ low_stock_items|default:0 }}</h3>
|
|
<p class="mb-0 opacity-75">Low Stock</p>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-exclamation-triangle fa-2x"></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" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
|
<div class="card-body text-white">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h3 class="mb-1 fw-bold">{{ expired_items|default:0 }}</h3>
|
|
<p class="mb-0 opacity-75">Expired Items</p>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-calendar-times fa-2x"></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" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
|
|
<div class="card-body text-white">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h3 class="mb-1 fw-bold">${{ total_value|default:0|floatformat:0 }}</h3>
|
|
<p class="mb-0 opacity-75">Total Value</p>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-dollar-sign fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alerts -->
|
|
{% if low_stock_items > 0 or expired_items > 0 %}
|
|
<div class="row mb-4">
|
|
{% if low_stock_items > 0 %}
|
|
<div class="col-md-6">
|
|
<div class="alert alert-warning d-flex align-items-center">
|
|
<i class="fas fa-exclamation-triangle me-3"></i>
|
|
<div>
|
|
<strong>{{ low_stock_items }} item{{ low_stock_items|pluralize }}</strong> below minimum stock level.
|
|
<a href="?filter=low_stock" class="alert-link">View items</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if expired_items > 0 %}
|
|
<div class="col-md-6">
|
|
<div class="alert alert-danger d-flex align-items-center">
|
|
<i class="fas fa-calendar-times me-3"></i>
|
|
<div>
|
|
<strong>{{ expired_items }} item{{ expired_items|pluralize }}</strong> have expired.
|
|
<a href="?filter=expired" class="alert-link">View items</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Search and Filters -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-filter me-2"></i>Search & Filter Inventory
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{# <form method="get" class="row g-3" id="filterForm">#}
|
|
{# <div class="col-lg-4 col-md-6">#}
|
|
{# <label for="search" class="form-label">Search Items</label>#}
|
|
{# <div class="input-group">#}
|
|
{# <span class="input-group-text"><i class="fas fa-search"></i></span>#}
|
|
{# <input type="text"#}
|
|
{# class="form-control"#}
|
|
{# id="search"#}
|
|
{# name="search"#}
|
|
{# value="{{ request.GET.search }}"#}
|
|
{# placeholder="Medication name, lot number, NDC..."#}
|
|
{# hx-get="{% url 'pharmacy:inventory_search' %}"#}
|
|
{# hx-target="#inventory-list-container"#}
|
|
{# hx-trigger="keyup changed delay:500ms"#}
|
|
{# hx-include="#filterForm">#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# #}
|
|
{# <div class="col-lg-2 col-md-6">#}
|
|
{# <label for="location" class="form-label">Location</label>#}
|
|
{# <select class="form-select" id="location" name="location"#}
|
|
{# hx-get="{% url 'pharmacy:inventory_search' %}"#}
|
|
{# hx-target="#inventory-list-container"#}
|
|
{# hx-trigger="change"#}
|
|
{# hx-include="#filterForm">#}
|
|
{# <option value="">All Locations</option>#}
|
|
{# {% for location in locations %}#}
|
|
{# <option value="{{ location.id }}" {% if request.GET.location == location.id|stringformat:"s" %}selected{% endif %}>{{ location.name }}</option>#}
|
|
{# {% endfor %}#}
|
|
{# </select>#}
|
|
{# </div>#}
|
|
{# #}
|
|
{# <div class="col-lg-2 col-md-6">#}
|
|
{# <label for="status" class="form-label">Status</label>#}
|
|
{# <select class="form-select" id="status" name="status"#}
|
|
{# hx-get="{% url 'pharmacy:inventory_search' %}"#}
|
|
{# hx-target="#inventory-list-container"#}
|
|
{# hx-trigger="change"#}
|
|
{# hx-include="#filterForm">#}
|
|
{# <option value="">All Status</option>#}
|
|
{# <option value="in_stock" {% if request.GET.status == 'in_stock' %}selected{% endif %}>In Stock</option>#}
|
|
{# <option value="low_stock" {% if request.GET.status == 'low_stock' %}selected{% endif %}>Low Stock</option>#}
|
|
{# <option value="out_of_stock" {% if request.GET.status == 'out_of_stock' %}selected{% endif %}>Out of Stock</option>#}
|
|
{# <option value="expired" {% if request.GET.status == 'expired' %}selected{% endif %}>Expired</option>#}
|
|
{# <option value="expiring_soon" {% if request.GET.status == 'expiring_soon' %}selected{% endif %}>Expiring Soon</option>#}
|
|
{# </select>#}
|
|
{# </div>#}
|
|
{# #}
|
|
{# <div class="col-lg-2 col-md-6">#}
|
|
{# <label for="controlled" class="form-label">Controlled</label>#}
|
|
{# <select class="form-select" id="controlled" name="controlled"#}
|
|
{# hx-get="{% url 'pharmacy:inventory_search' %}"#}
|
|
{# hx-target="#inventory-list-container"#}
|
|
{# hx-trigger="change"#}
|
|
{# hx-include="#filterForm">#}
|
|
{# <option value="">All Items</option>#}
|
|
{# <option value="yes" {% if request.GET.controlled == 'yes' %}selected{% endif %}>Controlled Only</option>#}
|
|
{# <option value="no" {% if request.GET.controlled == 'no' %}selected{% endif %}>Non-Controlled Only</option>#}
|
|
{# </select>#}
|
|
{# </div>#}
|
|
{# #}
|
|
{# <div class="col-lg-2 col-md-6">#}
|
|
{# <label class="form-label"> </label>#}
|
|
{# <div class="d-grid gap-2">#}
|
|
{# <button type="submit" class="btn btn-primary">#}
|
|
{# <i class="fas fa-search me-2"></i>Filter#}
|
|
{# </button>#}
|
|
{# <button type="button" class="btn btn-outline-secondary btn-sm" onclick="clearFilters()">#}
|
|
{# <i class="fas fa-times me-2"></i>Clear#}
|
|
{# </button>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </form>#}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Inventory List -->
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-list me-2"></i>Inventory Items
|
|
</h5>
|
|
<div class="d-flex align-items-center gap-3">
|
|
<span class="badge bg-primary">{{ page_obj.paginator.count|default:inventory_items.count }} total</span>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-primary" onclick="selectAll()">
|
|
<i class="fas fa-check-square me-1"></i>Select All
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="clearSelection()">
|
|
<i class="fas fa-square me-1"></i>Clear
|
|
</button>
|
|
</div>
|
|
<div class="dropdown">
|
|
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
<i class="fas fa-cog me-1"></i>Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="bulkAction('update_stock')">
|
|
<i class="fas fa-edit me-2"></i>Update Stock
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkAction('mark_expired')">
|
|
<i class="fas fa-calendar-times me-2"></i>Mark Expired
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkAction('export')">
|
|
<i class="fas fa-download me-2"></i>Export Selected
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-danger" href="#" onclick="bulkAction('delete')">
|
|
<i class="fas fa-trash me-2"></i>Delete Selected
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body p-0" id="inventory-list-container">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th width="40">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">
|
|
</div>
|
|
</th>
|
|
<th>Medication</th>
|
|
<th>Location</th>
|
|
<th>Current Stock</th>
|
|
<th>Min Level</th>
|
|
<th>Lot Number</th>
|
|
<th>Expiry Date</th>
|
|
<th>Unit Cost</th>
|
|
<th>Status</th>
|
|
<th width="120">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in inventory_items %}
|
|
<tr class="{% if item.is_expired %}table-danger{% elif item.is_low_stock %}table-warning{% elif item.is_expiring_soon %}table-info{% endif %}">
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="selected_items" value="{{ item.id }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="me-3">
|
|
{% if item.medication.is_controlled_substance %}
|
|
<i class="fas fa-shield-alt text-warning" title="Controlled Substance"></i>
|
|
{% else %}
|
|
<i class="fas fa-pills text-primary"></i>
|
|
{% endif %}
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold">{{ item.medication.generic_name }}</div>
|
|
{% if item.medication.brand_name %}
|
|
<small class="text-muted">{{ item.medication.brand_name }}</small>
|
|
{% endif %}
|
|
<br><small class="text-muted">{{ item.medication.strength }} {{ item.medication.get_unit_of_measure_display }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary">{{ item.location.name }}</span>
|
|
{% if item.location.description %}
|
|
<br><small class="text-muted">{{ item.location.description }}</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<span class="fw-bold {% if item.is_low_stock %}text-warning{% elif item.current_stock == 0 %}text-danger{% else %}text-success{% endif %}">
|
|
{{ item.current_stock }}
|
|
</span>
|
|
<span class="text-muted ms-1">{{ item.get_unit_of_measure_display }}</span>
|
|
</div>
|
|
{% if item.is_low_stock %}
|
|
<small class="text-warning">
|
|
<i class="fas fa-exclamation-triangle me-1"></i>Low Stock
|
|
</small>
|
|
{% elif item.current_stock == 0 %}
|
|
<small class="text-danger">
|
|
<i class="fas fa-times-circle me-1"></i>Out of Stock
|
|
</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="text-muted">{{ item.minimum_stock_level }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="font-monospace">{{ item.lot_number }}</span>
|
|
{% if item.supplier %}
|
|
<br><small class="text-muted">{{ item.supplier.name }}</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div>{{ item.expiry_date|date:"M d, Y" }}</div>
|
|
{% if item.is_expired %}
|
|
<small class="text-danger">
|
|
<i class="fas fa-times-circle me-1"></i>Expired
|
|
</small>
|
|
{% elif item.is_expiring_soon %}
|
|
<small class="text-warning">
|
|
<i class="fas fa-clock me-1"></i>Expiring Soon
|
|
</small>
|
|
{% else %}
|
|
<small class="text-muted">{{ item.days_until_expiry }} days</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if item.unit_cost %}
|
|
<div>${{ item.unit_cost|floatformat:2 }}</div>
|
|
<small class="text-muted">Total: ${{ item.total_value|floatformat:2 }}</small>
|
|
{% else %}
|
|
<span class="text-muted">N/A</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if item.is_expired %}
|
|
<span class="badge bg-danger">Expired</span>
|
|
{% elif item.current_stock == 0 %}
|
|
<span class="badge bg-danger">Out of Stock</span>
|
|
{% elif item.is_low_stock %}
|
|
<span class="badge bg-warning text-dark">Low Stock</span>
|
|
{% elif item.is_expiring_soon %}
|
|
<span class="badge bg-info">Expiring Soon</span>
|
|
{% else %}
|
|
<span class="badge bg-success">In Stock</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'pharmacy:inventory_detail' item.pk %}" class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="updateStock({{ item.pk }})" title="Update Stock">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-danger btn-sm" onclick="deleteItem({{ item.pk }})" title="Delete">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="10" class="text-center py-4">
|
|
<div class="text-muted">
|
|
<i class="fas fa-boxes fa-3x mb-3"></i>
|
|
<p class="mb-0">No inventory items found</p>
|
|
<small>Try adjusting your search criteria or add a new item</small>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<div class="card-footer">
|
|
<nav aria-label="Inventory pagination">
|
|
<ul class="pagination justify-content-center mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% 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 %}">{{ 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 %}">Next</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Update Stock Modal -->
|
|
<div class="modal fade" id="updateStockModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Update Stock</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="updateStockForm">
|
|
<div class="mb-3">
|
|
<label for="newStock" class="form-label">New Stock Level</label>
|
|
<input type="number" class="form-control" id="newStock" min="0" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="reason" class="form-label">Reason for Change</label>
|
|
<select class="form-select" id="reason" required>
|
|
<option value="">Select reason...</option>
|
|
<option value="received">Stock Received</option>
|
|
<option value="dispensed">Medication Dispensed</option>
|
|
<option value="expired">Expired Stock Removed</option>
|
|
<option value="damaged">Damaged Stock Removed</option>
|
|
<option value="adjustment">Inventory Adjustment</option>
|
|
<option value="other">Other</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="notes" class="form-label">Notes (Optional)</label>
|
|
<textarea class="form-control" id="notes" rows="3"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveStockUpdate()">Update Stock</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentItemId = null;
|
|
|
|
// Clear filters function
|
|
function clearFilters() {
|
|
document.getElementById('filterForm').reset();
|
|
window.location.href = '{% url "pharmacy:inventory_list" %}';
|
|
}
|
|
|
|
// Selection functions
|
|
function selectAll() {
|
|
const checkboxes = document.querySelectorAll('input[name="selected_items"]');
|
|
checkboxes.forEach(cb => cb.checked = true);
|
|
updateBulkActionButtons();
|
|
}
|
|
|
|
function clearSelection() {
|
|
const checkboxes = document.querySelectorAll('input[name="selected_items"]');
|
|
checkboxes.forEach(cb => cb.checked = false);
|
|
document.getElementById('selectAllCheckbox').checked = false;
|
|
updateBulkActionButtons();
|
|
}
|
|
|
|
function toggleSelectAll() {
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
const checkboxes = document.querySelectorAll('input[name="selected_items"]');
|
|
|
|
checkboxes.forEach(cb => cb.checked = selectAllCheckbox.checked);
|
|
updateBulkActionButtons();
|
|
}
|
|
|
|
function updateBulkActionButtons() {
|
|
const selectedCount = document.querySelectorAll('input[name="selected_items"]:checked').length;
|
|
const actionButtons = document.querySelectorAll('[onclick^="bulkAction"]');
|
|
|
|
actionButtons.forEach(btn => {
|
|
if (selectedCount > 0) {
|
|
btn.classList.remove('disabled');
|
|
btn.removeAttribute('disabled');
|
|
} else {
|
|
btn.classList.add('disabled');
|
|
btn.setAttribute('disabled', 'disabled');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Bulk actions
|
|
function bulkAction(action) {
|
|
const selectedItems = Array.from(document.querySelectorAll('input[name="selected_items"]:checked'))
|
|
.map(cb => cb.value);
|
|
|
|
if (selectedItems.length === 0) {
|
|
alert('Please select at least one item.');
|
|
return;
|
|
}
|
|
|
|
let confirmMessage = '';
|
|
switch (action) {
|
|
case 'update_stock':
|
|
confirmMessage = `Update stock for ${selectedItems.length} selected item(s)?`;
|
|
break;
|
|
case 'mark_expired':
|
|
confirmMessage = `Mark ${selectedItems.length} selected item(s) as expired?`;
|
|
break;
|
|
case 'export':
|
|
confirmMessage = `Export ${selectedItems.length} selected item(s)?`;
|
|
break;
|
|
case 'delete':
|
|
confirmMessage = `Delete ${selectedItems.length} selected item(s)? This action cannot be undone.`;
|
|
break;
|
|
}
|
|
|
|
if (confirm(confirmMessage)) {
|
|
// Implement bulk action logic here
|
|
console.log(`Performing ${action} on items:`, selectedItems);
|
|
}
|
|
}
|
|
|
|
// Update stock functions
|
|
function updateStock(itemId) {
|
|
currentItemId = itemId;
|
|
const modal = new bootstrap.Modal(document.getElementById('updateStockModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function saveStockUpdate() {
|
|
const newStock = document.getElementById('newStock').value;
|
|
const reason = document.getElementById('reason').value;
|
|
const notes = document.getElementById('notes').value;
|
|
|
|
if (!newStock || !reason) {
|
|
alert('Please fill in all required fields.');
|
|
return;
|
|
}
|
|
|
|
// Implement stock update logic here
|
|
fetch(`{% url 'pharmacy:update_inventory' 0 %}`.replace('0', currentItemId), {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
new_stock: newStock,
|
|
reason: reason,
|
|
notes: notes
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error updating stock: ' + data.error);
|
|
}
|
|
});
|
|
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('updateStockModal'));
|
|
modal.hide();
|
|
}
|
|
|
|
// Delete item
|
|
function deleteItem(itemId) {
|
|
if (confirm('Are you sure you want to delete this inventory item? This action cannot be undone.')) {
|
|
// Implement delete logic here
|
|
console.log('Deleting item:', itemId);
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
updateBulkActionButtons();
|
|
|
|
// Add event listeners to checkboxes
|
|
document.addEventListener('change', function(e) {
|
|
if (e.target.name === 'selected_items') {
|
|
updateBulkActionButtons();
|
|
|
|
// Update select all checkbox
|
|
const checkboxes = document.querySelectorAll('input[name="selected_items"]');
|
|
const checkedBoxes = document.querySelectorAll('input[name="selected_items"]:checked');
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
|
|
if (checkedBoxes.length === 0) {
|
|
selectAllCheckbox.indeterminate = false;
|
|
selectAllCheckbox.checked = false;
|
|
} else if (checkedBoxes.length === checkboxes.length) {
|
|
selectAllCheckbox.indeterminate = false;
|
|
selectAllCheckbox.checked = true;
|
|
} else {
|
|
selectAllCheckbox.indeterminate = true;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Keyboard shortcuts
|
|
document.addEventListener('keydown', function(e) {
|
|
// Ctrl+A to select all
|
|
if (e.ctrlKey && e.key === 'a' && e.target.tagName !== 'INPUT') {
|
|
e.preventDefault();
|
|
selectAll();
|
|
}
|
|
|
|
// Escape to clear selection
|
|
if (e.key === 'Escape') {
|
|
clearSelection();
|
|
}
|
|
|
|
// Ctrl+N for new item
|
|
if (e.ctrlKey && e.key === 'n') {
|
|
e.preventDefault();
|
|
window.location.href = '{% url "pharmacy:inventory_create" %}';
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.card {
|
|
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
|
border: 1px solid rgba(0, 0, 0, 0.125);
|
|
}
|
|
|
|
.card-header {
|
|
background-color: rgba(13, 110, 253, 0.1);
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
|
}
|
|
|
|
.btn {
|
|
border-radius: 0.375rem;
|
|
transition: all 0.15s ease-in-out;
|
|
}
|
|
|
|
.btn:hover:not(:disabled) {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.form-control, .form-select {
|
|
border-radius: 0.375rem;
|
|
border: 1px solid #ced4da;
|
|
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
|
}
|
|
|
|
.form-control:focus, .form-select:focus {
|
|
border-color: #86b7fe;
|
|
outline: 0;
|
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
|
}
|
|
|
|
.table th {
|
|
border-top: none;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
}
|
|
|
|
.table-hover tbody tr:hover {
|
|
background-color: rgba(13, 110, 253, 0.05);
|
|
}
|
|
|
|
.table-danger {
|
|
background-color: rgba(220, 53, 69, 0.1);
|
|
}
|
|
|
|
.table-warning {
|
|
background-color: rgba(255, 193, 7, 0.1);
|
|
}
|
|
|
|
.table-info {
|
|
background-color: rgba(13, 202, 240, 0.1);
|
|
}
|
|
|
|
.badge {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.fw-semibold {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.font-monospace {
|
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.btn-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.d-flex.justify-content-between {
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.card-header .d-flex {
|
|
flex-direction: column;
|
|
align-items: flex-start !important;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.table-responsive {
|
|
font-size: 0.875rem;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|