984 lines
46 KiB
PHP
984 lines
46 KiB
PHP
<?php
|
|
session_start();
|
|
require_once __DIR__ . '/db/config.php';
|
|
require_once __DIR__ . '/includes/lang.php';
|
|
|
|
$isAjax = isset($_POST['ajax']) || isset($_GET['ajax']) || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest');
|
|
|
|
if (!has_permission('view')) {
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => false, 'error' => 'Access Denied']);
|
|
exit;
|
|
}
|
|
die('Access Denied');
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
$action = $_POST['action'];
|
|
|
|
if ($action === 'add_item') {
|
|
$name_en = $_POST['name_en'];
|
|
$name_ar = $_POST['name_ar'];
|
|
$category_id = $_POST['category_id'] ?: null;
|
|
$vat_percent = (float)($_POST['vat_percent'] ?? 15.00);
|
|
$image_url = null;
|
|
|
|
// Handle Image Upload
|
|
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
|
$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, 0775, true);
|
|
if (move_uploaded_file($_FILES['image']['tmp_name'], $upload_dir . $filename)) {
|
|
$image_url = 'assets/images/items/' . $filename;
|
|
}
|
|
}
|
|
|
|
$stmt = db()->prepare("INSERT INTO items (name_en, name_ar, category_id, vat_percent, image_url) VALUES (?, ?, ?, ?, ?)");
|
|
$stmt->execute([$name_en, $name_ar, $category_id, $vat_percent, $image_url]);
|
|
|
|
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 = (float)($_POST['vat_percent'] ?? 15.00);
|
|
$image_url = $_POST['current_image_url'] ?? null;
|
|
|
|
// Handle Image Upload
|
|
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
|
$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, 0775, true);
|
|
if (move_uploaded_file($_FILES['image']['tmp_name'], $upload_dir . $filename)) {
|
|
$image_url = 'assets/images/items/' . $filename;
|
|
}
|
|
}
|
|
|
|
$stmt = db()->prepare("UPDATE items SET name_en = ?, name_ar = ?, category_id = ?, vat_percent = ?, image_url = ? WHERE id = ?");
|
|
$stmt->execute([$name_en, $name_ar, $category_id, $vat_percent, $image_url, $id]);
|
|
|
|
header('Location: items.php?success=item_updated');
|
|
exit;
|
|
} elseif ($action === 'delete_item') {
|
|
$id = $_POST['id'];
|
|
try {
|
|
$stmt = db()->prepare("DELETE FROM items WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
header('Location: items.php?success=item_deleted');
|
|
exit;
|
|
} catch (PDOException $e) {
|
|
header('Location: items.php?error=cannot_delete_item');
|
|
exit;
|
|
}
|
|
} elseif ($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)]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=category_added');
|
|
exit;
|
|
} elseif ($action === 'edit_category') {
|
|
$id = $_POST['id'];
|
|
$name_en = $_POST['cat_name_en'];
|
|
$name_ar = $_POST['cat_name_ar'];
|
|
$stmt = db()->prepare("UPDATE categories SET name_en = ?, name_ar = ? WHERE id = ?");
|
|
$stmt->execute([$name_en, $name_ar, $id]);
|
|
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true, 'html' => renderCategoryList($lang)]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=category_updated');
|
|
exit;
|
|
} elseif ($action === 'delete_category') {
|
|
$id = $_POST['id'];
|
|
try {
|
|
// Before deleting category, set items to null category
|
|
$stmt = db()->prepare("UPDATE items SET category_id = NULL WHERE category_id = ?");
|
|
$stmt->execute([$id]);
|
|
$stmt = db()->prepare("DELETE FROM categories WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true, 'html' => renderCategoryList($lang)]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=category_deleted');
|
|
exit;
|
|
} catch (PDOException $e) {
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => false, 'error' => 'Cannot delete category.']);
|
|
exit;
|
|
}
|
|
header('Location: items.php?error=cannot_delete_category');
|
|
exit;
|
|
}
|
|
} elseif ($action === 'add_service') {
|
|
$name_en = $_POST['svc_name_en'];
|
|
$name_ar = $_POST['svc_name_ar'];
|
|
$stmt = db()->prepare("INSERT INTO services (name_en, name_ar) VALUES (?, ?)");
|
|
$stmt->execute([$name_en, $name_ar]);
|
|
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true, 'html' => renderServiceList($lang)]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=service_added');
|
|
exit;
|
|
} elseif ($action === 'edit_service') {
|
|
$id = $_POST['id'];
|
|
$name_en = $_POST['svc_name_en'];
|
|
$name_ar = $_POST['svc_name_ar'];
|
|
$stmt = db()->prepare("UPDATE services SET name_en = ?, name_ar = ? WHERE id = ?");
|
|
$stmt->execute([$name_en, $name_ar, $id]);
|
|
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true, 'html' => renderServiceList($lang)]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=service_updated');
|
|
exit;
|
|
} elseif ($action === 'delete_service') {
|
|
$id = $_POST['id'];
|
|
try {
|
|
$stmt = db()->prepare("DELETE FROM services WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true, 'html' => renderServiceList($lang)]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=service_deleted');
|
|
exit;
|
|
} catch (PDOException $e) {
|
|
$stmt = db()->prepare("UPDATE services SET is_deleted = 1 WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true, 'html' => renderServiceList($lang)]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=service_deleted');
|
|
exit;
|
|
}
|
|
} elseif ($action === 'update_price') {
|
|
$item_id = $_POST['item_id'];
|
|
$service_id = $_POST['service_id'];
|
|
$price = $_POST['price'];
|
|
|
|
|
|
|
|
// Handle deletion if price is empty
|
|
if ($price === '') {
|
|
try {
|
|
// Delete ALL prices (both global and branch-specific) for this item and service.
|
|
// The user clicking "X" intends to remove this service from the item entirely.
|
|
$stmt = db()->prepare("DELETE FROM prices WHERE item_id = ? AND service_id = ?");
|
|
$stmt->execute([$item_id, $service_id]);
|
|
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?success=price_deleted');
|
|
exit;
|
|
} catch (Exception $e) {
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?error=delete_failed');
|
|
exit;
|
|
}
|
|
}
|
|
|
|
$price = str_replace(',', '.', $price); // Handle comma as decimal separator
|
|
$price = (float)$price;
|
|
|
|
try {
|
|
// Prices are globally shared
|
|
$chk = db()->prepare("SELECT id FROM prices WHERE item_id = ? AND service_id = ?");
|
|
$chk->execute([$item_id, $service_id]);
|
|
$existing = $chk->fetch();
|
|
|
|
if ($existing) {
|
|
$stmt = db()->prepare("UPDATE prices SET price = ?, branch_id = NULL WHERE id = ?");
|
|
$stmt->execute([$price, $existing['id']]);
|
|
} else {
|
|
$stmt = db()->prepare("INSERT INTO prices (item_id, service_id, price, branch_id) VALUES (?, ?, ?, NULL)");
|
|
$stmt->execute([$item_id, $service_id, $price]);
|
|
}
|
|
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => true]);
|
|
exit;
|
|
}
|
|
|
|
header('Location: items.php?success=price_updated');
|
|
exit;
|
|
} catch (Exception $e) {
|
|
if ($isAjax) {
|
|
header('Content-Type: application/json');
|
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
exit;
|
|
}
|
|
header('Location: items.php?error=save_failed');
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOW Include header
|
|
$title = 'items';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
|
|
// Search and Category Filter
|
|
$search = $_GET['search'] ?? '';
|
|
$cat_id = $_GET['category_id'] ?? '';
|
|
|
|
$current_branch_id = $_SESSION['branch_id'] ?? null;
|
|
|
|
// Fetch categories
|
|
$categories = db()->query("SELECT * FROM categories WHERE is_deleted = 0 ORDER BY name_en ASC")->fetchAll();
|
|
|
|
// Fetch services
|
|
$services = db()->query("SELECT * FROM services WHERE is_deleted = 0 ORDER BY name_en ASC")->fetchAll();
|
|
|
|
// Fetch items with filters
|
|
$where = [];
|
|
$params = [];
|
|
if ($search) {
|
|
$where[] = "(name_en LIKE ? OR name_ar LIKE ?)";
|
|
$params[] = "%$search%";
|
|
$params[] = "%$search%";
|
|
}
|
|
if ($cat_id) {
|
|
$where[] = "category_id = ?";
|
|
$params[] = $cat_id;
|
|
}
|
|
|
|
$where[] = "is_deleted = 0";
|
|
$where_sql = $where ? "WHERE " . implode(" AND ", $where) : "";
|
|
$stmt = db()->prepare("SELECT * FROM items $where_sql ORDER BY name_en ASC");
|
|
$stmt->execute($params);
|
|
$items = $stmt->fetchAll();
|
|
|
|
// Fetch prices (globally shared)
|
|
$stmt = db()->prepare("SELECT * FROM prices");
|
|
$stmt->execute();
|
|
$prices_raw = $stmt->fetchAll();
|
|
$prices = [];
|
|
foreach ($prices_raw as $p) {
|
|
$prices[$p['item_id']][$p['service_id']] = $p['price'];
|
|
}
|
|
|
|
function renderCategoryList($lang) {
|
|
$categories = db()->query("SELECT * FROM categories WHERE is_deleted = 0 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;
|
|
return ob_get_clean();
|
|
}
|
|
|
|
function renderServiceList($lang) {
|
|
$services = db()->query("SELECT * FROM services WHERE is_deleted = 0 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">
|
|
<div>
|
|
<h2 class="fw-bold mb-1"><?= __('items') ?></h2>
|
|
<p class="text-muted small mb-0"><?= __('manage_items_categories_services') ?? 'Manage your items, categories and services' ?></p>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" class="btn btn-light shadow-sm px-3" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#categoriesModal">
|
|
<i class="bi bi-tags me-2 text-primary"></i> <?= __('categories') ?>
|
|
</button>
|
|
<button type="button" class="btn btn-light shadow-sm px-3" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#servicesModal">
|
|
<i class="bi bi-gear me-2 text-primary"></i> <?= __('services') ?>
|
|
</button>
|
|
<?php if (has_permission('add')): ?>
|
|
<button type="button" class="btn btn-primary shadow-sm px-4" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#itemModal" onclick="resetForm()">
|
|
<i class="bi bi-plus-lg me-2"></i> <?= __('add_item') ?>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="card p-3 mb-4 border-0 shadow-sm" style="border-radius: 20px;">
|
|
<form class="row g-3">
|
|
<div class="col-md-5">
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-white border-0"><i class="bi bi-search"></i></span>
|
|
<input type="text" name="search" class="form-control border-0" placeholder="<?= __('search_items') ?>" value="<?= htmlspecialchars($search) ?>">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<select name="category_id" class="form-select border-0">
|
|
<option value=""><?= __('all_categories') ?></option>
|
|
<?php foreach($categories as $cat): ?>
|
|
<option value="<?= $cat['id'] ?>" <?= $cat_id == $cat['id'] ? 'selected' : '' ?>>
|
|
<?= $lang === 'ar' ? $cat['name_ar'] : $cat['name_en'] ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<button type="submit" class="btn btn-primary w-100 fw-bold rounded-4 h-100"><?= __('filter') ?></button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Items List -->
|
|
<div class="card border-0 shadow-sm mb-4" style="border-radius: 20px; overflow: hidden;">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4 py-3 border-0" style="width: 80px;"><?= __('image') ?? 'Image' ?></th>
|
|
<th class="py-3 border-0"><?= __('name') ?? 'Name' ?></th>
|
|
<th class="py-3 border-0"><?= __('category') ?? 'Category' ?></th>
|
|
<th class="py-3 border-0"><?= __('services') ?? 'Services' ?></th>
|
|
<th class="pe-4 py-3 border-0 text-end"><?= __('actions') ?? 'Actions' ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach($items as $item): ?>
|
|
<tr>
|
|
<td class="ps-4 py-3 border-bottom">
|
|
<?php if ($item['image_url']): ?>
|
|
<img src="<?= $item['image_url'] ?>" class="rounded-3 shadow-sm" style="width: 50px; height: 50px; object-fit: cover;">
|
|
<?php else: ?>
|
|
<div class="bg-light rounded-3 d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
|
<i class="bi bi-image text-muted"></i>
|
|
</div>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="py-3 border-bottom fw-bold">
|
|
<?= $lang === 'ar' ? $item['name_ar'] : $item['name_en'] ?>
|
|
</td>
|
|
<td class="py-3 border-bottom text-muted small">
|
|
<?php
|
|
$cat_name = '';
|
|
foreach($categories as $cat) {
|
|
if ($cat['id'] == $item['category_id']) {
|
|
$cat_name = $lang === 'ar' ? $cat['name_ar'] : $cat['name_en'];
|
|
break;
|
|
}
|
|
}
|
|
echo $cat_name ?: __('uncategorized');
|
|
?>
|
|
</td>
|
|
<td class="py-3 border-bottom">
|
|
<button class="btn btn-sm btn-primary text-white fw-bold rounded-pill px-3 shadow-sm d-inline-flex align-items-center gap-1" data-bs-toggle="modal" data-bs-target="#pricingModal<?= $item['id'] ?>">
|
|
<i class="bi bi-tags"></i>
|
|
<?= count($prices[$item['id']] ?? []) ?> <?= __('services') ?>
|
|
</button>
|
|
</td>
|
|
<td class="pe-4 py-3 border-bottom text-end">
|
|
<div class="d-inline-flex gap-1">
|
|
<?php if (has_permission('edit')): ?>
|
|
<button class="btn btn-sm btn-light p-2 rounded-3 text-primary border border-primary border-opacity-25" onclick='editItem(<?= json_encode($item) ?>)' title="<?= __('edit') ?>">
|
|
<i class="bi bi-pencil px-1"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
<?php if (has_permission('delete')): ?>
|
|
<button class="btn btn-sm btn-light p-2 rounded-3 text-danger border border-danger border-opacity-25" onclick="confirmDeleteItem(<?= $item['id'] ?>)" title="<?= __('delete') ?>">
|
|
<i class="bi bi-trash px-1"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (empty($items)): ?>
|
|
<div class="text-center p-5">
|
|
<i class="bi bi-inbox text-muted" style="font-size: 4rem;"></i>
|
|
<p class="text-muted mt-3"><?= __('no_items_found') ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Item Modal -->
|
|
<div class="modal fade" id="itemModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content" style="border-radius: 30px;">
|
|
<div class="modal-header border-0 p-4 pb-0">
|
|
<h5 class="modal-title fw-bold" id="itemModalTitle"><?= __('add_new_item') ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form id="itemForm" method="POST" enctype="multipart/form-data">
|
|
<input type="hidden" name="action" id="itemAction" value="add_item">
|
|
<input type="hidden" name="id" id="itemId">
|
|
<input type="hidden" name="current_image_url" id="itemCurrentImageUrl">
|
|
<div class="modal-body p-4">
|
|
<div class="mb-4 text-center">
|
|
<div id="imagePreviewContainer" class="d-none mb-3">
|
|
<img id="imagePreview" src="" class="rounded-4 shadow-sm" style="max-height: 150px; max-width: 100%; object-fit: cover;">
|
|
</div>
|
|
<label for="itemImageFile" class="btn btn-light rounded-4 px-4 py-3 w-100 border-2 border-dashed" style="border-style: dashed !important;">
|
|
<i class="bi bi-cloud-upload me-2 fs-4"></i>
|
|
<div class="small fw-bold"><?= __('upload_image') ?></div>
|
|
<input type="file" name="image" id="itemImageFile" class="d-none" accept="image/*">
|
|
</label>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label small fw-bold"><?= __('name_en') ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="name_en" id="itemNameEn" class="form-control" required style="border-radius: 12px 0 0 12px;">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="translateField('itemNameEn', 'itemNameAr', 'en-ar')" style="border-radius: 0 12px 12px 0;">
|
|
<i class="bi bi-translate"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small fw-bold"><?= __('name_ar') ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="name_ar" id="itemNameAr" class="form-control text-end" required style="border-radius: 12px 0 0 12px;">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="translateField('itemNameAr', 'itemNameEn', 'ar-en')" style="border-radius: 0 12px 12px 0;">
|
|
<i class="bi bi-translate"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<label class="form-label small fw-bold"><?= __('category') ?></label>
|
|
<select name="category_id" id="itemCategoryId" class="form-select" style="border-radius: 12px;">
|
|
<option value=""><?= __('select_category') ?></option>
|
|
<?php foreach($categories as $cat): ?>
|
|
<option value="<?= $cat['id'] ?>"><?= $lang === 'ar' ? $cat['name_ar'] : $cat['name_en'] ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label small fw-bold"><?= __('vat_percent') ?></label>
|
|
<input type="number" step="0.01" name="vat_percent" id="itemVatPercent" class="form-control" value="15.00" style="border-radius: 12px;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0">
|
|
<button type="button" class="btn btn-light rounded-4 px-4" data-bs-dismiss="modal"><?= __('cancel') ?></button>
|
|
<button type="submit" class="btn btn-primary rounded-4 px-4 fw-bold shadow-sm"><?= __('save') ?></button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Categories Modal -->
|
|
<div class="modal fade" id="categoriesModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
<div class="modal-content" style="border-radius: 25px;">
|
|
<div class="modal-header border-0 p-4">
|
|
<h5 class="modal-title fw-bold"><?= __('categories_management') ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body p-4 pt-0">
|
|
<div class="row g-4">
|
|
<div class="col-md-5">
|
|
<form id="categoryForm" method="POST">
|
|
<input type="hidden" name="action" id="catAction" value="add_category">
|
|
<input type="hidden" name="id" id="catId">
|
|
<input type="hidden" name="ajax" value="1">
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold"><?= __('name_en') ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="cat_name_en" id="catNameEn" class="form-control" required>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="translateField('catNameEn', 'catNameAr', 'en-ar')">
|
|
<i class="bi bi-translate"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold"><?= __('name_ar') ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="cat_name_ar" id="catNameAr" class="form-control text-end" required>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="translateField('catNameAr', 'catNameEn', 'ar-en')">
|
|
<i class="bi bi-translate"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100 fw-bold" style="border-radius: 10px;">
|
|
<span id="catSubmitBtn"><?= __('add_category') ?></span>
|
|
</button>
|
|
<button type="button" id="catCancelBtn" class="btn btn-light w-100 mt-2 d-none" onclick="resetCatForm()" style="border-radius: 10px;">
|
|
<?= __('cancel') ?>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="col-md-7">
|
|
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
|
<table class="table table-hover small">
|
|
<thead>
|
|
<tr>
|
|
<th><?= __('name_en') ?></th>
|
|
<th><?= __('name_ar') ?></th>
|
|
<th class="text-end"><?= __('actions') ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="categoryTableBody">
|
|
<?= renderCategoryList($lang) ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Services Modal -->
|
|
<div class="modal fade" id="servicesModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
<div class="modal-content" style="border-radius: 25px;">
|
|
<div class="modal-header border-0 p-4">
|
|
<h5 class="modal-title fw-bold"><?= __('services_management') ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body p-4 pt-0">
|
|
<div class="row g-4">
|
|
<div class="col-md-5">
|
|
<form id="serviceForm" method="POST">
|
|
<input type="hidden" name="action" id="svcAction" value="add_service">
|
|
<input type="hidden" name="id" id="svcId">
|
|
<input type="hidden" name="ajax" value="1">
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold"><?= __('name_en') ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="svc_name_en" id="svcNameEn" class="form-control" required>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="translateField('svcNameEn', 'svcNameAr', 'en-ar')">
|
|
<i class="bi bi-translate"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold"><?= __('name_ar') ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="svc_name_ar" id="svcNameAr" class="form-control text-end" required>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="translateField('svcNameAr', 'svcNameEn', 'ar-en')">
|
|
<i class="bi bi-translate"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100 fw-bold" style="border-radius: 10px;">
|
|
<span id="svcSubmitBtn"><?= __('add_service') ?></span>
|
|
</button>
|
|
<button type="button" id="svcCancelBtn" class="btn btn-light w-100 mt-2 d-none" onclick="resetSvcForm()" style="border-radius: 10px;">
|
|
<?= __('cancel') ?>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="col-md-7">
|
|
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
|
<table class="table table-hover small">
|
|
<thead>
|
|
<tr>
|
|
<th><?= __('name_en') ?></th>
|
|
<th><?= __('name_ar') ?></th>
|
|
<th class="text-end"><?= __('actions') ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="serviceTableBody">
|
|
<?= renderServiceList($lang) ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pricing Modals -->
|
|
<?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">
|
|
<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="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
|
|
$assigned_services = [];
|
|
$unassigned_services = [];
|
|
foreach($services as $svc) {
|
|
if (isset($prices[$item['id']][$svc['id']])) {
|
|
$assigned_services[] = $svc;
|
|
} else {
|
|
$unassigned_services[] = $svc;
|
|
}
|
|
}
|
|
?>
|
|
|
|
<h6 class="fw-bold mb-3 small text-success"><?= __('assigned_services') ?? 'Assigned Services' ?></h6>
|
|
<div class="assigned-list">
|
|
<?php if (empty($assigned_services)): ?>
|
|
<div class="text-muted small mb-3 fst-italic no-services-msg"><?= __('no_services_assigned') ?? 'No services assigned to this item.' ?></div>
|
|
<?php endif; ?>
|
|
<?php foreach($assigned_services as $svc): ?>
|
|
<div class="mb-3 d-flex align-items-center justify-content-between p-3 bg-white border border-success border-opacity-25 shadow-sm service-row" style="border-radius: 15px;" id="row-<?= $item['id'] ?>-<?= $svc['id'] ?>">
|
|
<div class="fw-bold small">
|
|
<?= $lang === 'ar' ? $svc['name_ar'] : $svc['name_en'] ?>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2" style="width: 200px;">
|
|
<input type="number" step="0.001"
|
|
class="form-control form-control-sm border bg-light 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">
|
|
<button type="button" class="btn btn-sm btn-light p-1 remove-btn" style="border-radius: 8px;" onclick="removePrice(this, <?= $item['id'] ?>, <?= $svc['id'] ?>)">
|
|
<i class="bi bi-x-circle text-danger"></i>
|
|
</button>
|
|
<i class="bi bi-check-circle-fill text-success ms-1 success-indicator d-none"></i>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<h6 class="fw-bold mb-3 mt-4 small text-muted"><?= __('available_services') ?? 'Available Services' ?></h6>
|
|
<div class="unassigned-list">
|
|
<?php foreach($unassigned_services as $svc): ?>
|
|
<div class="mb-3 d-flex align-items-center justify-content-between p-3 bg-light service-row" style="border-radius: 15px;" id="row-<?= $item['id'] ?>-<?= $svc['id'] ?>">
|
|
<div class="fw-bold small text-muted">
|
|
<?= $lang === 'ar' ? $svc['name_ar'] : $svc['name_en'] ?>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2" style="width: 200px;">
|
|
<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=""
|
|
placeholder="<?= __('add') ?? 'Add...' ?>">
|
|
<button type="button" class="btn btn-sm btn-light p-1 remove-btn" style="border-radius: 8px; display: none;" onclick="removePrice(this, <?= $item['id'] ?>, <?= $svc['id'] ?>)">
|
|
<i class="bi bi-x-circle text-danger"></i>
|
|
</button>
|
|
<i class="bi bi-check-circle-fill text-success ms-1 success-indicator d-none"></i>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<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; ?>
|
|
|
|
<script>
|
|
function translateField(sourceId, targetId, direction) {
|
|
const text = document.getElementById(sourceId).value;
|
|
if (!text) return;
|
|
|
|
const formData = new FormData();
|
|
formData.append("text", text);
|
|
formData.append("direction", direction);
|
|
|
|
fetch("api/translate.php", {
|
|
method: "POST",
|
|
body: formData
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById(targetId).value = data.translation;
|
|
}
|
|
});
|
|
}
|
|
|
|
function resetForm() {
|
|
document.getElementById('itemForm').reset();
|
|
document.getElementById('itemAction').value = 'add_item';
|
|
document.getElementById('itemModalTitle').innerText = '<?= __('add_new_item') ?>';
|
|
document.getElementById('imagePreviewContainer').classList.add('d-none');
|
|
}
|
|
|
|
function editItem(item) {
|
|
resetForm();
|
|
document.getElementById('itemAction').value = 'edit_item';
|
|
document.getElementById('itemId').value = item.id;
|
|
document.getElementById('itemNameEn').value = item.name_en;
|
|
document.getElementById('itemNameAr').value = item.name_ar;
|
|
document.getElementById('itemCategoryId').value = item.category_id;
|
|
document.getElementById('itemVatPercent').value = item.vat_percent;
|
|
document.getElementById('itemCurrentImageUrl').value = item.image_url;
|
|
document.getElementById('itemModalTitle').innerText = '<?= __('edit_item') ?>';
|
|
|
|
if (item.image_url) {
|
|
document.getElementById('imagePreview').src = item.image_url;
|
|
document.getElementById('imagePreviewContainer').classList.remove('d-none');
|
|
}
|
|
|
|
new bootstrap.Modal(document.getElementById('itemModal')).show();
|
|
}
|
|
|
|
function confirmDeleteItem(id) {
|
|
if (confirm('<?= __('confirm_delete') ?>')) {
|
|
const form = document.createElement('form');
|
|
form.method = 'POST';
|
|
form.innerHTML = `<input type="hidden" name="action" value="delete_item"><input type="hidden" name="id" value="${id}">`;
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
}
|
|
}
|
|
|
|
function editCategory(cat) {
|
|
document.getElementById('catAction').value = 'edit_category';
|
|
document.getElementById('catId').value = cat.id;
|
|
document.getElementById('catNameEn').value = cat.name_en;
|
|
document.getElementById('catNameAr').value = cat.name_ar;
|
|
document.getElementById('catSubmitBtn').innerText = '<?= __('save') ?>';
|
|
document.getElementById('catCancelBtn').classList.remove('d-none');
|
|
}
|
|
|
|
function resetCatForm() {
|
|
document.getElementById('categoryForm').reset();
|
|
document.getElementById('catAction').value = 'add_category';
|
|
document.getElementById('catSubmitBtn').innerText = '<?= __('add_category') ?>';
|
|
document.getElementById('catCancelBtn').classList.add('d-none');
|
|
}
|
|
|
|
function editService(svc) {
|
|
document.getElementById('svcAction').value = 'edit_service';
|
|
document.getElementById('svcId').value = svc.id;
|
|
document.getElementById('svcNameEn').value = svc.name_en;
|
|
document.getElementById('svcNameAr').value = svc.name_ar;
|
|
document.getElementById('svcSubmitBtn').innerText = '<?= __('save') ?>';
|
|
document.getElementById('svcCancelBtn').classList.remove('d-none');
|
|
}
|
|
|
|
function resetSvcForm() {
|
|
document.getElementById('serviceForm').reset();
|
|
document.getElementById('svcAction').value = 'add_service';
|
|
document.getElementById('svcSubmitBtn').innerText = '<?= __('add_service') ?>';
|
|
document.getElementById('svcCancelBtn').classList.add('d-none');
|
|
}
|
|
|
|
function confirmDeleteAjax(type, id) {
|
|
if (confirm('<?= __('confirm_delete') ?>')) {
|
|
const formData = new FormData();
|
|
formData.append('action', 'delete_' + type);
|
|
formData.append('id', id);
|
|
formData.append('ajax', '1');
|
|
|
|
fetch('items.php', { method: 'POST', body: formData })
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (type === 'category') document.getElementById('categoryTableBody').innerHTML = data.html;
|
|
if (type === 'service') document.getElementById('serviceTableBody').innerHTML = data.html;
|
|
} else {
|
|
alert(data.error || 'Failed to delete');
|
|
}
|
|
}).catch(err => {
|
|
alert('An error occurred while deleting.');
|
|
});
|
|
}
|
|
}
|
|
|
|
document.getElementById('categoryForm')?.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
try {
|
|
const response = await fetch('items.php', { method: 'POST', body: formData });
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
document.getElementById('categoryTableBody').innerHTML = data.html;
|
|
resetCatForm();
|
|
}
|
|
} catch (error) {}
|
|
});
|
|
|
|
document.getElementById('serviceForm')?.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
try {
|
|
const response = await fetch('items.php', { method: 'POST', body: formData });
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
document.getElementById('serviceTableBody').innerHTML = data.html;
|
|
resetSvcForm();
|
|
} else {
|
|
alert(data.error || 'Failed to save service');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving service:', error);
|
|
}
|
|
});
|
|
|
|
// AJAX logic for prices
|
|
document.querySelectorAll('.price-input').forEach(input => {
|
|
let timeout = null;
|
|
input.addEventListener('input', function() {
|
|
if (this.readOnly || this.disabled) return;
|
|
clearTimeout(timeout);
|
|
const self = this;
|
|
timeout = setTimeout(async () => {
|
|
const formData = new FormData();
|
|
formData.append('action', 'update_price');
|
|
formData.append('ajax', '1');
|
|
formData.append('item_id', self.dataset.itemId);
|
|
formData.append('service_id', self.dataset.serviceId);
|
|
formData.append('price', self.value);
|
|
|
|
try {
|
|
const response = await fetch('items.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
showIndicator(self);
|
|
const row = self.closest('.service-row');
|
|
const assignedList = row.closest('.modal-body').querySelector('.assigned-list');
|
|
if (row && assignedList && row.parentElement.classList.contains('unassigned-list')) {
|
|
row.classList.remove('bg-light');
|
|
row.classList.add('border', 'border-success', 'border-opacity-25', 'bg-white', 'shadow-sm');
|
|
const titleDiv = row.querySelector('.fw-bold.small');
|
|
if(titleDiv) titleDiv.classList.remove('text-muted');
|
|
const noMsg = assignedList.querySelector('.no-services-msg');
|
|
if(noMsg) noMsg.style.display = 'none';
|
|
assignedList.appendChild(row);
|
|
const btn = row.querySelector('.remove-btn');
|
|
if (btn) btn.style.display = 'inline-block';
|
|
}
|
|
} else {
|
|
alert(data.error || 'Failed to update price');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating price:', error);
|
|
alert('Error updating price: ' + error.message);
|
|
}
|
|
}, 500);
|
|
});
|
|
});
|
|
|
|
async function removePrice(btn, itemId, serviceId) {
|
|
if (confirm('<?= __('confirm_delete') ?>')) {
|
|
const input = btn.parentElement.querySelector('.price-input');
|
|
const formData = new FormData();
|
|
formData.append('action', 'update_price');
|
|
formData.append('ajax', '1');
|
|
formData.append('item_id', itemId);
|
|
formData.append('service_id', serviceId);
|
|
formData.append('price', ''); // Empty price triggers deletion
|
|
|
|
try {
|
|
const response = await fetch('items.php', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
input.value = '';
|
|
showIndicator(input);
|
|
// Move visually to available services
|
|
const row = btn.closest('.service-row');
|
|
if (row) {
|
|
row.classList.remove('border', 'border-success', 'border-opacity-25', 'bg-white', 'shadow-sm');
|
|
row.classList.add('bg-light');
|
|
const titleDiv = row.querySelector('.fw-bold.small');
|
|
if(titleDiv) titleDiv.classList.add('text-muted');
|
|
|
|
const unassignedList = row.closest('.modal-body').querySelector('.unassigned-list');
|
|
if (unassignedList) {
|
|
unassignedList.appendChild(row);
|
|
btn.style.display = 'none'; // hide X button
|
|
}
|
|
}
|
|
} else {
|
|
alert(data.error || 'Failed to remove price');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error removing price:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function showIndicator(input) {
|
|
const indicator = input.parentElement.querySelector('.success-indicator');
|
|
if (indicator) {
|
|
indicator.classList.remove('d-none');
|
|
setTimeout(() => {
|
|
indicator.classList.add('d-none');
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
// Image preview logic
|
|
document.getElementById('itemImageFile').addEventListener('change', function(e) {
|
|
const file = e.target.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
document.getElementById('imagePreview').src = e.target.result;
|
|
document.getElementById('imagePreviewContainer').classList.remove('d-none');
|
|
}
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|