352 lines
16 KiB
PHP
352 lines
16 KiB
PHP
<?php
|
|
$page = $_GET['page'] ?? 1;
|
|
$search = $_GET['search'] ?? '';
|
|
|
|
// Fetch drugs with stock info
|
|
// The API 'get_stock' returns all, but for pagination we might want to do it in PHP or adjust API.
|
|
// For now, let's use the API approach via JS or just fetch in PHP.
|
|
// Since we are in PHP page, fetching directly is better for SEO/Speed than pure AJAX sometimes, but standard here seems mixed.
|
|
// I'll fetch via PHP for initial render.
|
|
|
|
$limit = 10;
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
$where = "WHERE 1=1";
|
|
$params = [];
|
|
if ($search) {
|
|
$where .= " AND (d.name_en LIKE ? OR d.name_ar LIKE ?)";
|
|
$params[] = "%$search%";
|
|
$params[] = "%$search%";
|
|
}
|
|
|
|
// Count
|
|
$count_stmt = $db->prepare("SELECT COUNT(*) FROM drugs d $where");
|
|
$count_stmt->execute($params);
|
|
$total_rows = $count_stmt->fetchColumn();
|
|
$total_pages = ceil($total_rows / $limit);
|
|
|
|
// Fetch
|
|
$sql = "SELECT d.id, d.name_en, d.name_ar, d.min_stock_level, d.reorder_level, d.unit,
|
|
COALESCE(SUM(b.quantity), 0) as total_stock
|
|
FROM drugs d
|
|
LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE()
|
|
$where
|
|
GROUP BY d.id
|
|
ORDER BY d.name_en ASC
|
|
LIMIT $limit OFFSET $offset";
|
|
$stmt = $db->prepare($sql);
|
|
$stmt->execute($params);
|
|
$drugs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
?>
|
|
|
|
<div class="container-fluid">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2 class="fw-bold text-primary"><i class="bi bi-boxes me-2"></i> <?php echo __('inventory'); ?></h2>
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addStockModal">
|
|
<i class="bi bi-plus-lg me-1"></i> <?php echo __('add_stock'); ?>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<form method="GET" class="row g-3 mb-4">
|
|
<div class="col-md-4">
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-light border-end-0"><i class="bi bi-search text-muted"></i></span>
|
|
<input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="<?php echo __('search'); ?>..." value="<?php echo htmlspecialchars($search); ?>">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="submit" class="btn btn-primary w-100"><?php echo __('search'); ?></button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th><?php echo __('drug_name'); ?></th>
|
|
<th><?php echo __('stock'); ?></th>
|
|
<th><?php echo __('unit'); ?></th>
|
|
<th><?php echo __('status'); ?></th>
|
|
<th><?php echo __('actions'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($drugs)): ?>
|
|
<tr>
|
|
<td colspan="5" class="text-center py-4 text-muted">
|
|
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
|
|
<?php echo __('no_data_found'); ?>
|
|
</td>
|
|
</tr>
|
|
<?php else: ?>
|
|
<?php foreach ($drugs as $drug): ?>
|
|
<?php
|
|
$status_class = 'bg-success';
|
|
$status_text = __('in_stock');
|
|
if ($drug['total_stock'] <= 0) {
|
|
$status_class = 'bg-danger';
|
|
$status_text = __('out_of_stock');
|
|
} elseif ($drug['total_stock'] <= $drug['min_stock_level']) {
|
|
$status_class = 'bg-warning text-dark';
|
|
$status_text = __('low_stock');
|
|
}
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<div class="fw-bold"><?php echo htmlspecialchars($drug['name_en']); ?></div>
|
|
<small class="text-muted"><?php echo htmlspecialchars($drug['name_ar']); ?></small>
|
|
</td>
|
|
<td>
|
|
<span class="fs-5 fw-bold"><?php echo $drug['total_stock']; ?></span>
|
|
</td>
|
|
<td><?php echo htmlspecialchars($drug['unit'] ?? '-'); ?></td>
|
|
<td>
|
|
<span class="badge <?php echo $status_class; ?> rounded-pill">
|
|
<?php echo $status_text; ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-info" onclick="viewBatches(<?php echo $drug['id']; ?>, '<?php echo addslashes($drug['name_en']); ?>')">
|
|
<i class="bi bi-eye"></i> <?php echo __('view_batches'); ?>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<?php if ($total_pages > 1): ?>
|
|
<nav aria-label="Page navigation" class="mt-4">
|
|
<ul class="pagination justify-content-center">
|
|
<li class="page-item <?php echo $page <= 1 ? 'disabled' : ''; ?>">
|
|
<a class="page-link" href="?page=<?php echo max(1, $page - 1); ?>&search=<?php echo urlencode($search); ?>"><?php echo __('previous'); ?></a>
|
|
</li>
|
|
|
|
<?php
|
|
$range = 2;
|
|
|
|
// First page
|
|
if ($page == 1) {
|
|
echo '<li class="page-item active"><span class="page-link">1</span></li>';
|
|
} else {
|
|
echo '<li class="page-item"><a class="page-link" href="?page=1&search='.urlencode($search).'">1</a></li>';
|
|
}
|
|
|
|
// Start of range
|
|
$start = max(2, $page - $range);
|
|
|
|
// End of range
|
|
$end = min($total_pages - 1, $page + $range);
|
|
|
|
// Dots before range
|
|
if ($start > 2) {
|
|
echo '<li class="page-item disabled"><span class="page-link">...</span></li>';
|
|
}
|
|
|
|
// Range loop
|
|
for ($i = $start; $i <= $end; $i++) {
|
|
if ($i == $page) {
|
|
echo '<li class="page-item active"><span class="page-link">'.$i.'</span></li>';
|
|
} else {
|
|
echo '<li class="page-item"><a class="page-link" href="?page='.$i.'&search='.urlencode($search).'">'.$i.'</a></li>';
|
|
}
|
|
}
|
|
|
|
// Dots after range
|
|
if ($end < $total_pages - 1) {
|
|
echo '<li class="page-item disabled"><span class="page-link">...</span></li>';
|
|
}
|
|
|
|
// Last page (if total > 1)
|
|
if ($total_pages > 1) {
|
|
if ($page == $total_pages) {
|
|
echo '<li class="page-item active"><span class="page-link">'.$total_pages.'</span></li>';
|
|
} else {
|
|
echo '<li class="page-item"><a class="page-link" href="?page='.$total_pages.'&search='.urlencode($search).'">'.$total_pages.'</a></li>';
|
|
}
|
|
}
|
|
?>
|
|
|
|
<li class="page-item <?php echo $page >= $total_pages ? 'disabled' : ''; ?>">
|
|
<a class="page-link" href="?page=<?php echo min($total_pages, $page + 1); ?>&search=<?php echo urlencode($search); ?>"><?php echo __('next'); ?></a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Stock Modal -->
|
|
<div class="modal fade" id="addStockModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><?php echo __('add_stock'); ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="addStockForm">
|
|
<div class="mb-3">
|
|
<label class="form-label"><?php echo __('select_drug'); ?></label>
|
|
<select class="form-select select2" name="drug_id" required style="width: 100%;">
|
|
<option value=""><?php echo __('select_drug'); ?></option>
|
|
<?php
|
|
$all_drugs = $db->query("SELECT id, name_en FROM drugs ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($all_drugs as $d) {
|
|
echo "<option value='{$d['id']}'>" . htmlspecialchars($d['name_en']) . "</option>";
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label"><?php echo __('batch_number'); ?></label>
|
|
<input type="text" class="form-control" name="batch_number" required>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label"><?php echo __('expiry_date'); ?></label>
|
|
<input type="date" class="form-control" name="expiry_date" required>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label"><?php echo __('quantity'); ?></label>
|
|
<input type="number" class="form-control" name="quantity" min="1" required>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label"><?php echo __('cost_price'); ?></label>
|
|
<input type="number" class="form-control" name="cost_price" step="0.01" value="0">
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label"><?php echo __('sale_price'); ?></label>
|
|
<input type="number" class="form-control" name="sale_price" step="0.01" value="0">
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label"><?php echo __('supplier'); ?></label>
|
|
<select class="form-select" name="supplier_id">
|
|
<option value=""><?php echo __('select_supplier'); ?></option>
|
|
<?php
|
|
$suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
|
|
foreach ($suppliers as $s) {
|
|
echo "<option value='{$s['id']}'>" . htmlspecialchars($s['name_en']) . "</option>";
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
|
<button type="button" class="btn btn-primary" onclick="submitStock()"><?php echo __('save'); ?></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- View Batches Modal -->
|
|
<div class="modal fade" id="viewBatchesModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="batchesModalTitle"><?php echo __('view_batches'); ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php echo __('batch_number'); ?></th>
|
|
<th><?php echo __('expiry_date'); ?></th>
|
|
<th><?php echo __('quantity'); ?></th>
|
|
<th><?php echo __('cost_price'); ?></th>
|
|
<th><?php echo __('supplier'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="batchesTableBody">
|
|
<!-- Populated via JS -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function submitStock() {
|
|
const form = document.getElementById('addStockForm');
|
|
if (!form.checkValidity()) {
|
|
form.reportValidity();
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData(form);
|
|
|
|
fetch('api/pharmacy.php?action=add_stock', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert(data.error || 'Error adding stock');
|
|
}
|
|
})
|
|
.catch(e => {
|
|
console.error(e);
|
|
alert('Network error');
|
|
});
|
|
}
|
|
|
|
function viewBatches(drugId, drugName) {
|
|
document.getElementById('batchesModalTitle').textContent = drugName + ' - Batches';
|
|
const tbody = document.getElementById('batchesTableBody');
|
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center">Loading...</td></tr>';
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('viewBatchesModal'));
|
|
modal.show();
|
|
|
|
fetch('api/pharmacy.php?action=get_batches&drug_id=' + drugId)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
tbody.innerHTML = '';
|
|
if (data.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center">No active batches found</td></tr>';
|
|
return;
|
|
}
|
|
|
|
data.forEach(batch => {
|
|
tbody.innerHTML += `
|
|
<tr>
|
|
<td>${batch.batch_number}</td>
|
|
<td>${batch.expiry_date}</td>
|
|
<td>${batch.quantity}</td>
|
|
<td>${batch.cost_price}</td>
|
|
<td>${batch.supplier_name || '-'}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
})
|
|
.catch(e => {
|
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-danger">Error loading data</td></tr>';
|
|
});
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
$('.select2').select2({
|
|
theme: 'bootstrap-5',
|
|
dropdownParent: $('#addStockModal')
|
|
});
|
|
});
|
|
</script>
|