update items

This commit is contained in:
Flatlogic Bot 2026-03-03 18:08:33 +00:00
parent a7d442be5d
commit 35d8fe23f8

452
items.php
View File

@ -1,205 +1,38 @@
<?php
// ACTION HANDLING FIRST (to allow redirects)
ob_start();
session_start();
$action = $_POST['action'] ?? $_GET['action'] ?? '';
$isAjax = isset($_POST['ajax']) || isset($_GET['ajax']) || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.php';
$isAjax = isset($_POST['ajax']) || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
if (!isset($_SESSION['user_id'])) {
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
header('Location: login.php');
exit;
}
// Initial view check
if (!has_permission('view')) {
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Forbidden']);
exit;
}
header('Location: admin.php');
exit;
}
$branch_id = $_SESSION['branch_id'];
function handleImageUpload($file) {
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
return null;
}
$targetDir = "assets/images/items/";
if (!is_dir($targetDir)) {
mkdir($targetDir, 0775, true);
}
$fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$newFileName = uniqid('item_', true) . '.' . $fileExtension;
$targetFile = $targetDir . $newFileName;
// Check if image file is a actual image or fake image
$check = getimagesize($file['tmp_name']);
if($check === false) {
return null;
}
// Allow certain file formats
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($fileExtension, $allowedExtensions)) {
return null;
}
if (move_uploaded_file($file['tmp_name'], $targetFile)) {
return $targetFile;
}
return null;
}
function deleteOldImage($imageUrl) {
if ($imageUrl && strpos($imageUrl, 'assets/images/items/') === 0) {
if (file_exists($imageUrl)) {
unlink($imageUrl);
}
}
}
// Helper to render category list HTML for AJAX
function renderCategoryList($lang) {
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll();
$html = '';
foreach($categories as $cat) {
$html .= '<tr>
<td>
<strong>' . htmlspecialchars($cat['name_en']) . '</strong><br>
<small class="text-muted">' . htmlspecialchars($cat['name_ar']) . '</small>
</td>
<td class="text-end">
';
if (has_permission('edit')) {
$html .= '<button type="button" class="btn btn-sm btn-light p-2 me-1" style="border-radius: 8px;" onclick="editCategory(' . htmlspecialchars(json_encode($cat), ENT_QUOTES) . ')">
<i class="bi bi-pencil text-primary"></i>
</button>';
}
if (has_permission('delete')) {
$html .= '<button type="button" class="btn btn-sm btn-light p-2" style="border-radius: 8px;" onclick="confirmDeleteAjax(\'category\', ' . $cat['id'] . ')">
<i class="bi bi-trash text-danger"></i>
</button>';
}
$html .= '</td></tr>';
}
return $html;
}
// Helper to render service list HTML for AJAX
function renderServiceList($lang) {
$services = db()->query("SELECT * FROM services ORDER BY name_en ASC")->fetchAll();
$html = '';
foreach($services as $svc) {
$html .= '<tr>
<td>
<strong>' . htmlspecialchars($svc['name_en']) . '</strong><br>
<small class="text-muted">' . htmlspecialchars($svc['name_ar']) . '</small>
</td>
<td class="text-end">
';
if (has_permission('edit')) {
$html .= '<button type="button" class="btn btn-sm btn-light p-2 me-1" style="border-radius: 8px;" onclick="editService(' . htmlspecialchars(json_encode($svc), ENT_QUOTES) . ')">
<i class="bi bi-pencil text-primary"></i>
</button>';
}
if (has_permission('delete')) {
$html .= '<button type="button" class="btn btn-sm btn-light p-2" style="border-radius: 8px;" onclick="confirmDeleteAjax(\'service\', ' . $svc['id'] . ')">
<i class="bi bi-trash text-danger"></i>
</button>';
}
$html .= '</td></tr>';
}
return $html;
}
// Handle Actions
// Action handling
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
// Permission mapping for actions
$required_permission = 'view';
if (strpos($action, 'add_') === 0) $required_permission = 'add';
if (strpos($action, 'edit_') === 0 || strpos($action, 'update_') === 0) $required_permission = 'edit';
if (strpos($action, 'delete_') === 0) $required_permission = 'delete';
if (!has_permission($required_permission)) {
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Forbidden']);
exit;
}
header('Location: items.php?error=no_permission');
exit;
}
if ($action === 'add_item') {
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$category_id = $_POST['category_id'] ?: null;
$vat_percent = $_POST['vat_percent'] ?: 0.00;
$image_url = handleImageUpload($_FILES['image'] ?? null);
$stmt = db()->prepare("INSERT INTO items (category_id, name_en, name_ar, image_url, vat_percent) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$category_id, $name_en, $name_ar, $image_url, $vat_percent]);
header('Location: items.php?success=item_added');
exit;
} elseif ($action === 'edit_item') {
$id = $_POST['id'];
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$category_id = $_POST['category_id'] ?: null;
$vat_percent = $_POST['vat_percent'] ?: 0.00;
// Get current image to delete it if new one is uploaded
$stmt = db()->prepare("SELECT image_url FROM items WHERE id = ?");
$stmt->execute([$id]);
$currentItem = $stmt->fetch();
$image_url = $currentItem['image_url'] ?? null;
$new_image = handleImageUpload($_FILES['image'] ?? null);
if ($new_image) {
deleteOldImage($image_url);
$image_url = $new_image;
}
$stmt = db()->prepare("UPDATE items SET category_id = ?, name_en = ?, name_ar = ?, image_url = ?, vat_percent = ? WHERE id = ?");
$stmt->execute([$category_id, $name_en, $name_ar, $image_url, $vat_percent, $id]);
header('Location: items.php?success=item_updated');
exit;
} elseif ($action === 'delete_item') {
$id = $_POST['id'];
// Delete image file if exists
$stmt = db()->prepare("SELECT image_url FROM items WHERE id = ?");
$stmt->execute([$id]);
$item = $stmt->fetch();
deleteOldImage($item['image_url'] ?? null);
$stmt = db()->prepare("DELETE FROM items WHERE id = ?");
$stmt->execute([$id]);
header('Location: items.php?success=item_deleted');
exit;
} elseif ($action === 'add_category') {
if ($action === 'add_category') {
$name_en = $_POST['cat_name_en'];
$name_ar = $_POST['cat_name_ar'];
$stmt = db()->prepare("INSERT INTO categories (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$name_en, $name_ar]);
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode(['success' => true, 'html' => renderCategoryList($lang)]);
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll();
ob_start();
foreach($categories as $cat): ?>
<tr>
<td><?= $cat['name_en'] ?></td>
<td><?= $cat['name_ar'] ?></td>
<td class="text-end">
<button class="btn btn-sm btn-light p-1 me-1" onclick='editCategory(<?= json_encode($cat) ?>)'><i class="bi bi-pencil text-primary"></i></button>
<button class="btn btn-sm btn-light p-1" onclick="confirmDeleteAjax('category', <?= $cat['id'] ?>)"><i class="bi bi-trash text-danger"></i></button>
</td>
</tr>
<?php endforeach;
$html = ob_get_clean();
echo json_encode(['success' => true, 'html' => $html]);
exit;
}
header('Location: items.php?success=category_added');
@ -213,7 +46,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode(['success' => true, 'html' => renderCategoryList($lang)]);
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll();
ob_start();
foreach($categories as $cat): ?>
<tr>
<td><?= $cat['name_en'] ?></td>
<td><?= $cat['name_ar'] ?></td>
<td class="text-end">
<button class="btn btn-sm btn-light p-1 me-1" onclick='editCategory(<?= json_encode($cat) ?>)'><i class="bi bi-pencil text-primary"></i></button>
<button class="btn btn-sm btn-light p-1" onclick="confirmDeleteAjax('category', <?= $cat['id'] ?>)"><i class="bi bi-trash text-danger"></i></button>
</td>
</tr>
<?php endforeach;
$html = ob_get_clean();
echo json_encode(['success' => true, 'html' => $html]);
exit;
}
header('Location: items.php?success=category_updated');
@ -225,11 +71,78 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($isAjax) {
header('Content-Type: application/json');
echo json_encode(['success' => true, 'html' => renderCategoryList($lang)]);
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll();
ob_start();
foreach($categories as $cat): ?>
<tr>
<td><?= $cat['name_en'] ?></td>
<td><?= $cat['name_ar'] ?></td>
<td class="text-end">
<button class="btn btn-sm btn-light p-1 me-1" onclick='editCategory(<?= json_encode($cat) ?>)'><i class="bi bi-pencil text-primary"></i></button>
<button class="btn btn-sm btn-light p-1" onclick="confirmDeleteAjax('category', <?= $cat['id'] ?>)"><i class="bi bi-trash text-danger"></i></button>
</td>
</tr>
<?php endforeach;
$html = ob_get_clean();
echo json_encode(['success' => true, 'html' => $html]);
exit;
}
header('Location: items.php?success=category_deleted');
exit;
} elseif ($action === 'add_item') {
$category_id = $_POST['category_id'] ?: null;
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$vat_percent = $_POST['vat_percent'] ?: 0;
$image_url = null;
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = 'item_' . uniqid('', true) . '.' . $ext;
$upload_dir = __DIR__ . '/assets/images/items/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);
if (move_uploaded_file($_FILES['image']['tmp_name'], $upload_dir . $filename)) {
$image_url = 'assets/images/items/' . $filename;
}
}
$stmt = db()->prepare("INSERT INTO items (category_id, name_en, name_ar, vat_percent, image_url) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$category_id, $name_en, $name_ar, $vat_percent, $image_url]);
header('Location: items.php?success=item_added');
exit;
} elseif ($action === 'edit_item') {
$id = $_POST['id'];
$category_id = $_POST['category_id'] ?: null;
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$vat_percent = $_POST['vat_percent'] ?: 0;
$image_url = null;
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = 'item_' . uniqid('', true) . '.' . $ext;
$upload_dir = __DIR__ . '/assets/images/items/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);
if (move_uploaded_file($_FILES['image']['tmp_name'], $upload_dir . $filename)) {
$image_url = 'assets/images/items/' . $filename;
}
}
if ($image_url) {
$stmt = db()->prepare("UPDATE items SET category_id = ?, name_en = ?, name_ar = ?, vat_percent = ?, image_url = ? WHERE id = ?");
$stmt->execute([$category_id, $name_en, $name_ar, $vat_percent, $image_url, $id]);
} else {
$stmt = db()->prepare("UPDATE items SET category_id = ?, name_en = ?, name_ar = ?, vat_percent = ? WHERE id = ?");
$stmt->execute([$category_id, $name_en, $name_ar, $vat_percent, $id]);
}
header('Location: items.php?success=item_updated');
exit;
} elseif ($action === 'delete_item') {
$id = $_POST['id'];
$stmt = db()->prepare("DELETE FROM items WHERE id = ?");
$stmt->execute([$id]);
header('Location: items.php?success=item_deleted');
exit;
} elseif ($action === 'add_service') {
$name_en = $_POST['svc_name_en'];
$name_ar = $_POST['svc_name_ar'];
@ -279,11 +192,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$price = str_replace(',', '.', $price); // Handle comma as decimal separator
$price = (float)$price;
// Determine branch_id from session (if available)
$branch_id = $_SESSION['branch_id'] ?? null;
if ($branch_id === 'all') $branch_id = null;
// Final sanity check for branch_id to avoid FK errors if session has stale ID
if ($branch_id) {
$chk = db()->prepare("SELECT id FROM branches WHERE id = ?");
$chk->execute([$branch_id]);
if (!$chk->fetch()) $branch_id = null;
}
try {
$stmt = db()->prepare("INSERT INTO prices (item_id, service_id, price)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE price = ?");
$stmt->execute([$item_id, $service_id, $price, $price]);
if ($branch_id) {
$stmt = db()->prepare("INSERT INTO prices (item_id, service_id, price, branch_id)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE price = ?");
$stmt->execute([$item_id, $service_id, $price, $branch_id, $price]);
} else {
$stmt = db()->prepare("INSERT INTO prices (item_id, service_id, price)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE price = ?");
$stmt->execute([$item_id, $service_id, $price, $price]);
}
if ($isAjax) {
header('Content-Type: application/json');
@ -350,6 +281,22 @@ $prices = [];
foreach ($prices_raw as $p) {
$prices[$p['item_id']][$p['service_id']] = $p['price'];
}
function renderServiceList($lang) {
$services = db()->query("SELECT * FROM services ORDER BY name_en ASC")->fetchAll();
ob_start();
foreach($services as $svc): ?>
<tr>
<td><?= $svc['name_en'] ?></td>
<td><?= $svc['name_ar'] ?></td>
<td class="text-end">
<button class="btn btn-sm btn-light p-1 me-1" onclick='editService(<?= json_encode($svc) ?>)'><i class="bi bi-pencil text-primary"></i></button>
<button class="btn btn-sm btn-light p-1" onclick="confirmDeleteAjax('service', <?= $svc['id'] ?>)"><i class="bi bi-trash text-danger"></i></button>
</td>
</tr>
<?php endforeach;
return ob_get_clean();
}
?>
<div class="d-flex justify-content-between align-items-center mb-4">
@ -558,15 +505,25 @@ foreach ($prices_raw as $p) {
</div>
<div class="col-md-7">
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-hover align-middle">
<thead class="bg-light">
<table class="table table-hover small">
<thead>
<tr>
<th><?= __('name') ?></th>
<th><?= __('name_en') ?></th>
<th><?= __('name_ar') ?></th>
<th class="text-end"><?= __('actions') ?></th>
</tr>
</thead>
<tbody id="categoryTableBody">
<?= renderCategoryList($lang) ?>
<?php foreach($categories as $cat): ?>
<tr>
<td><?= $cat['name_en'] ?></td>
<td><?= $cat['name_ar'] ?></td>
<td class="text-end">
<button class="btn btn-sm btn-light p-1 me-1" onclick='editCategory(<?= json_encode($cat) ?>)'><i class="bi bi-pencil text-primary"></i></button>
<button class="btn btn-sm btn-light p-1" onclick="confirmDeleteAjax('category', <?= $cat['id'] ?>)"><i class="bi bi-trash text-danger"></i></button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
@ -621,10 +578,11 @@ foreach ($prices_raw as $p) {
</div>
<div class="col-md-7">
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-hover align-middle">
<thead class="bg-light">
<table class="table table-hover small">
<thead>
<tr>
<th><?= __('name') ?></th>
<th><?= __('name_en') ?></th>
<th><?= __('name_ar') ?></th>
<th class="text-end"><?= __('actions') ?></th>
</tr>
</thead>
@ -641,80 +599,58 @@ foreach ($prices_raw as $p) {
</div>
<!-- Pricing Modals -->
<?php foreach($items as $item):
?><div class="modal fade pricing-modal" id="pricingModal<?= $item['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered">
<?php foreach($items as $item): ?>
<div class="modal fade" id="pricingModal<?= $item['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 25px;">
<div class="modal-header border-0 p-4">
<div>
<h5 class="modal-title fw-bold m-0"><?= __('service_pricing') ?? 'Service Pricing' ?></h5>
<p class="text-muted m-0 small"><?= $lang === 'ar' ? $item['name_ar'] : $item['name_en'] ?></p>
</div>
<h5 class="modal-title fw-bold">
<?= __('set_prices_for') ?> <?= $lang === 'ar' ? $item['name_ar'] : $item['name_en'] ?>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4 pt-0">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="bg-light">
<tr class="small text-muted">
<th style="min-width: 200px;"><?= __('service') ?></th>
<th class="text-center"><?= __('price') ?> (<?= currency() ?>)</th>
</tr>
</thead>
<tbody>
<?php foreach($services as $service):
?><tr>
<td class="fw-bold">
<?= $lang === 'ar' ? $service['name_ar'] : $service['name_en'] ?>
</td>
<td class="text-center">
<div class="input-group input-group-sm justify-content-center mx-auto" style="width: 150px; position: relative;">
<input type="number" step="0.001" class="form-control text-center price-input"
value="<?= number_format($prices[$item['id']][$service['id']] ?? 0, decimals(), '.', '') ?>"
data-item-id="<?= $item['id'] ?>"
data-service-id="<?= $service['id'] ?>"
style="border-radius: 8px;"
<?= !has_permission('edit') ? 'readonly disabled' : '' ?>>
<span class="input-group-text bg-transparent border-0 d-none success-indicator" style="position: absolute; right: -25px; top: 5px;">
<i class="bi bi-check-lg text-success"></i>
</span>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="alert alert-info py-2 small mt-3 mb-0" style="border-radius: 10px;">
<i class="bi bi-info-circle me-2"></i>
<?= has_permission('edit') ? 'Prices are saved automatically as you type.' : 'You can view prices but cannot edit them.' ?>
<div class="alert alert-light small border-0 mb-4" style="border-radius: 15px;">
<i class="bi bi-info-circle me-2"></i><?= __('prices_saved_automatically') ?>
</div>
<?php foreach($services as $svc): ?>
<div class="mb-3 d-flex align-items-center justify-content-between p-3 bg-light" style="border-radius: 15px;">
<div class="fw-bold small">
<?= $lang === 'ar' ? $svc['name_ar'] : $svc['name_en'] ?>
</div>
<div class="d-flex align-items-center" style="width: 150px;">
<input type="number" step="0.001"
class="form-control form-control-sm border-0 bg-white price-input"
style="border-radius: 10px;"
data-item-id="<?= $item['id'] ?>"
data-service-id="<?= $svc['id'] ?>"
value="<?= $prices[$item['id']][$svc['id']] ?? '' ?>"
placeholder="0.000">
<i class="bi bi-check-circle-fill text-success ms-2 success-indicator d-none"></i>
</div>
</div>
<?php endforeach; ?>
<button type="button" class="btn btn-dark w-100 py-3 fw-bold mt-3 shadow-sm" style="border-radius: 15px;" data-bs-dismiss="modal">
<?= __('done') ?>
</button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<!-- Delete Confirmation Form -->
<form id="deleteForm" method="POST" style="display: none;">
<form id="deleteForm" method="POST" class="d-none">
<input type="hidden" name="action" id="deleteAction">
<input type="hidden" name="id" id="deleteId">
</form>
<style>
.bg-soft-primary { background-color: rgba(13, 110, 253, 0.1); }
.price-input:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.1);
}
.pricing-modal .table th { border-top: none; }
</style>
<script>
async function translateField(sourceId, targetId, direction) {
const sourceEl = document.getElementById(sourceId);
const targetEl = document.getElementById(targetId);
const text = sourceEl.value.trim();
const text = sourceEl.value;
if (!text) return;
const btn = event.currentTarget;
const originalHtml = btn.innerHTML;
@ -892,4 +828,4 @@ document.getElementById('itemImageFile').addEventListener('change', function(e)
});
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>
<?php require_once __DIR__ . '/includes/footer.php'; ?>