diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..4688093 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,139 @@ +:root { + --sidebar-width: 250px; +} body { - background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); - background-size: 400% 400%; - animation: gradient 15s ease infinite; - color: #212529; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - margin: 0; - min-height: 100vh; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f4f6f9; } - -.main-wrapper { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; +#wrapper { + overflow-x: hidden; +} +#sidebar-wrapper { + min-height: 100vh; + margin-left: calc(-1 * var(--sidebar-width)); + transition: margin .25s ease-out; + width: var(--sidebar-width); + position: fixed; + top: 0; + left: 0; + z-index: 1000; + background: #343a40; /* Dark theme sidebar */ +} +[dir="rtl"] #sidebar-wrapper { + margin-left: 0; + margin-right: calc(-1 * var(--sidebar-width)); + left: auto; + right: 0; +} +#wrapper.toggled #sidebar-wrapper { + margin-left: 0; +} +[dir="rtl"] #wrapper.toggled #sidebar-wrapper { + margin-right: 0; +} +#page-content-wrapper { + min-width: 100vw; + transition: margin .25s ease-out; +} +@media (min-width: 768px) { + #sidebar-wrapper { + margin-left: 0; + } + [dir="rtl"] #sidebar-wrapper { + margin-right: 0; + } + #page-content-wrapper { + min-width: 0; width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; + margin-left: var(--sidebar-width); + } + [dir="rtl"] #page-content-wrapper { + margin-left: 0; + margin-right: var(--sidebar-width); + } + #wrapper.toggled #sidebar-wrapper { + margin-left: calc(-1 * var(--sidebar-width)); + } + [dir="rtl"] #wrapper.toggled #sidebar-wrapper { + margin-right: calc(-1 * var(--sidebar-width)); + } + #wrapper.toggled #page-content-wrapper { + margin-left: 0; + } + [dir="rtl"] #wrapper.toggled #page-content-wrapper { + margin-right: 0; + } } -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } +.sidebar-heading { + padding: 1rem 1.25rem; + font-size: 1.25rem; + color: #fff; + background: #212529; +} +.list-group-item { + border: none; + padding: 0.85rem 1.25rem; + background-color: transparent; + color: #c2c7d0; + font-weight: 500; +} +.list-group-item:hover, .list-group-item.active { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; +} +.list-group-item i { + margin-right: 10px; + width: 20px; + text-align: center; +} +[dir="rtl"] .list-group-item i { + margin-right: 0; + margin-left: 10px; } -.chat-container { - width: 100%; - max-width: 600px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 20px; - display: flex; - flex-direction: column; - height: 85vh; - box-shadow: 0 20px 40px rgba(0,0,0,0.2); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - overflow: hidden; +.top-navbar { + background-color: #fff; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); } -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; +.card { + box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2); + margin-bottom: 1rem; + border: 0; + border-radius: 0.25rem; } - -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; -} - -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; -} - -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); -} - -.message { - max-width: 85%; - padding: 0.85rem 1.1rem; - border-radius: 16px; - line-height: 1.5; - font-size: 0.95rem; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); - animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } -} - -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; -} - -.message.bot { - align-self: flex-start; - background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; -} - -.chat-input-area { - padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); -} - -.chat-input-area form { - display: flex; - gap: 0.75rem; -} - -.chat-input-area input { - flex: 1; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - padding: 0.75rem 1rem; - outline: none; - background: rgba(255, 255, 255, 0.9); - transition: all 0.3s ease; -} - -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); -} - -.chat-input-area button { - background: #212529; - color: #fff; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; -} - -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); -} - -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; -} - -.blob { - position: absolute; - width: 500px; - height: 500px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - filter: blur(80px); - animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1); -} - -.blob-1 { - top: -10%; - left: -10%; - background: rgba(238, 119, 82, 0.4); -} - -.blob-2 { - bottom: -10%; - right: -10%; - background: rgba(35, 166, 213, 0.4); - animation-delay: -7s; - width: 600px; - height: 600px; -} - -.blob-3 { - top: 40%; - left: 30%; - background: rgba(231, 60, 126, 0.3); - animation-delay: -14s; - width: 450px; - height: 450px; -} - -@keyframes move { - 0% { transform: translate(0, 0) rotate(0deg) scale(1); } - 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); } - 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); } - 100% { transform: translate(0, 0) rotate(360deg) scale(1); } -} - -.header-link { - font-size: 14px; - color: #fff; - text-decoration: none; - background: rgba(0, 0, 0, 0.2); - padding: 0.5rem 1rem; - border-radius: 8px; - transition: all 0.3s ease; -} - -.header-link:hover { - background: rgba(0, 0, 0, 0.4); - text-decoration: none; -} - -/* Admin Styles */ -.admin-container { - max-width: 900px; - margin: 3rem auto; - padding: 2.5rem; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: 0 20px 50px rgba(0,0,0,0.15); - border: 1px solid rgba(255, 255, 255, 0.4); - position: relative; - z-index: 1; -} - -.admin-container h1 { - margin-top: 0; - color: #212529; - font-weight: 800; -} - -.table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; +.card-header { + background-color: transparent; + border-bottom: 1px solid rgba(0,0,0,.125); + font-weight: 600; } .table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; - font-weight: 600; - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 1px; + font-weight: 600; + color: #495057; + background-color: #f8f9fa; } -.table td { - background: #fff; - padding: 1rem; - border: none; +/* Modal styles */ +.modal-header { + background-color: #f8f9fa; + border-bottom: 1px solid #dee2e6; +} +.modal-footer { + border-top: 1px solid #dee2e6; + background-color: #f8f9fa; } -.table tr td:first-child { border-radius: 12px 0 0 12px; } -.table tr td:last-child { border-radius: 0 12px 12px 0; } - -.form-group { - margin-bottom: 1.25rem; +body.auth-body { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + background-color: #e9ecef; } -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 0.9rem; -} - -.form-control { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - background: #fff; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); -} - -.header-container { - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-links { - display: flex; - gap: 1rem; -} - -.admin-card { - background: rgba(255, 255, 255, 0.6); - padding: 2rem; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.5); - margin-bottom: 2.5rem; - box-shadow: 0 10px 30px rgba(0,0,0,0.05); -} - -.admin-card h3 { - margin-top: 0; - margin-bottom: 1.5rem; - font-weight: 700; -} - -.btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; - cursor: pointer; -} - -.btn-add { - background: #212529; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - margin-top: 1rem; -} - -.btn-save { - background: #0088cc; - color: white; - border: none; - padding: 0.8rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - width: 100%; - transition: all 0.3s ease; -} - -.webhook-url { - font-size: 0.85em; - color: #555; - margin-top: 0.5rem; -} - -.history-table-container { - overflow-x: auto; - background: rgba(255, 255, 255, 0.4); - padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.3); -} - -.history-table { - width: 100%; -} - -.history-table-time { - width: 15%; - white-space: nowrap; - font-size: 0.85em; - color: #555; -} - -.history-table-user { - width: 35%; - background: rgba(255, 255, 255, 0.3); - border-radius: 8px; - padding: 8px; -} - -.history-table-ai { - width: 50%; - background: rgba(255, 255, 255, 0.5); - border-radius: 8px; - padding: 8px; -} - -.no-messages { - text-align: center; - color: #777; +/* Sidebar Sub-menu */ +[data-bs-toggle="collapse"][aria-expanded="true"] .toggle-icon { + transform: rotate(180deg); } \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index d349598..b21d2c4 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,125 @@ +// Basic logic for cart interactions document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); + const saleForm = document.querySelector('[data-sale-form]'); + if (!saleForm) return; - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; - }; + let cart = []; + const cartJsonInput = document.getElementById('cart_json'); + const cartLinesContainer = document.getElementById('cart-lines'); + const cartEmptyState = document.getElementById('cart-empty-state'); + const cartCountLabel = document.getElementById('cart-count-label'); + const cartSubtotalLabel = document.getElementById('cart-subtotal'); + const cartTotalLabel = document.getElementById('cart-total'); - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; + function renderCart() { + if (cart.length === 0) { + cartEmptyState.style.display = 'block'; + cartLinesContainer.innerHTML = ''; + cartCountLabel.textContent = '0 ' + (window.saleLabels ? window.saleLabels.empty : 'items'); + cartSubtotalLabel.textContent = '0.00'; + cartTotalLabel.textContent = '0.00'; + cartJsonInput.value = '[]'; + return; + } - appendMessage(message, 'visitor'); - chatInput.value = ''; + cartEmptyState.style.display = 'none'; + let html = ''; + let totalItems = 0; + let subtotal = 0; - try { - const response = await fetch('api/chat.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) - }); - const data = await response.json(); - - // Artificial delay for realism - setTimeout(() => { - appendMessage(data.reply, 'bot'); - }, 500); - } catch (error) { - console.error('Error:', error); - appendMessage("Sorry, something went wrong. Please try again.", 'bot'); - } + cart.forEach((item, index) => { + const lineTotal = item.qty * item.price; + totalItems += item.qty; + subtotal += lineTotal; + + html += ` +
+
+
${item.name}
+
${item.qty} x ${item.price.toFixed(3)}
+
+
+ ${lineTotal.toFixed(3)} +
+ + + +
+
+
+ `; }); -}); + + cartLinesContainer.innerHTML = html; + cartCountLabel.textContent = totalItems + ' items'; + cartSubtotalLabel.textContent = subtotal.toFixed(3); + cartTotalLabel.textContent = subtotal.toFixed(3); + cartJsonInput.value = JSON.stringify(cart); + } + + window.updateCartQty = function(index, delta) { + if (cart[index]) { + cart[index].qty += delta; + if (cart[index].qty <= 0) { + cart.splice(index, 1); + } + renderCart(); + } + }; + + window.removeCartItem = function(index) { + cart.splice(index, 1); + renderCart(); + }; + + document.querySelectorAll('[data-add-product]').forEach(btn => { + btn.addEventListener('click', () => { + const sku = btn.dataset.sku; + const name = btn.dataset.name; + const price = parseFloat(btn.dataset.price); + + const existing = cart.find(i => i.sku === sku); + if (existing) { + existing.qty++; + } else { + cart.push({ sku, name, price, qty: 1 }); + } + + // SweetAlert2 Toast for adding product + if (typeof Swal !== 'undefined') { + Swal.fire({ + toast: true, + position: 'top-end', + icon: 'success', + title: name + ' added', + showConfirmButton: false, + timer: 1500 + }); + } + + renderCart(); + }); + }); + + const clearBtn = document.querySelector('[data-clear-cart]'); + if (clearBtn) { + clearBtn.addEventListener('click', () => { + cart = []; + renderCart(); + }); + } + + // Handle form submission warning if empty + saleForm.addEventListener('submit', (e) => { + if (cart.length === 0) { + e.preventDefault(); + if (typeof Swal !== 'undefined') { + Swal.fire('Empty Cart', 'Please add items before saving.', 'warning'); + } else { + alert('Cart is empty'); + } + } + }); + + renderCart(); +}); \ No newline at end of file diff --git a/categories.php b/categories.php new file mode 100644 index 0000000..a99ccd1 --- /dev/null +++ b/categories.php @@ -0,0 +1,226 @@ +prepare('INSERT INTO categories (name_ar, name_en, description) VALUES (?, ?, ?)'); + $stmt->execute([$_POST['name_ar'], $_POST['name_en'], $_POST['description'] ?? '']); + set_flash('success', tr('تمت إضافة التصنيف بنجاح', 'Category added successfully')); + redirect_to('categories.php'); + } elseif ($action === 'edit') { + $stmt = $pdo->prepare('UPDATE categories SET name_ar = ?, name_en = ?, description = ? WHERE id = ?'); + $stmt->execute([$_POST['name_ar'], $_POST['name_en'], $_POST['description'] ?? '', $_POST['id']]); + set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully')); + redirect_to('categories.php'); + } elseif ($action === 'delete') { + $stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?'); + $stmt->execute([$_POST['id']]); + set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully')); + redirect_to('categories.php'); + } +} + +// Pagination & Search +$page = max(1, (int)($_GET['p'] ?? 1)); +$limit = 10; +$offset = ($page - 1) * $limit; +$search = $_GET['q'] ?? ''; + +$where = '1=1'; +$params = []; +if ($search) { + $where .= ' AND (name_ar LIKE ? OR name_en LIKE ?)'; + $params[] = "%$search%"; + $params[] = "%$search%"; +} + +$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM categories WHERE $where"); +$totalStmt->execute($params); +$total = $totalStmt->fetchColumn(); +$totalPages = ceil($total / $limit); + +$queryStmt = $pdo->prepare("SELECT * FROM categories WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset"); +$queryStmt->execute($params); +$items = $queryStmt->fetchAll(); + +require __DIR__ . '/includes/header.php'; +?> + +
+
+
+

+

+
+ +
+ +
+
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
ID
+ + +
+
+ + 1): ?> + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/customers.php b/customers.php new file mode 100644 index 0000000..30db595 --- /dev/null +++ b/customers.php @@ -0,0 +1,242 @@ +prepare('INSERT INTO customers (name, phone, email, address) VALUES (?, ?, ?, ?)'); + $stmt->execute([$_POST['name'], $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '']); + set_flash('success', tr('تمت إضافة العميل بنجاح', 'Customer added successfully')); + redirect_to('customers.php'); + } elseif ($action === 'edit') { + $stmt = $pdo->prepare('UPDATE customers SET name = ?, phone = ?, email = ?, address = ? WHERE id = ?'); + $stmt->execute([$_POST['name'], $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '', $_POST['id']]); + set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully')); + redirect_to('customers.php'); + } elseif ($action === 'delete') { + $stmt = $pdo->prepare('DELETE FROM customers WHERE id = ?'); + $stmt->execute([$_POST['id']]); + set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully')); + redirect_to('customers.php'); + } +} + +// Pagination & Search +$page = max(1, (int)($_GET['p'] ?? 1)); +$limit = 10; +$offset = ($page - 1) * $limit; +$search = $_GET['q'] ?? ''; + +$where = '1=1'; +$params = []; +if ($search) { + $where .= ' AND (name LIKE ? OR phone LIKE ? OR email LIKE ?)'; + $params[] = "%$search%"; + $params[] = "%$search%"; + $params[] = "%$search%"; +} + +$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM customers WHERE $where"); +$totalStmt->execute($params); +$total = $totalStmt->fetchColumn(); +$totalPages = ceil($total / $limit); + +$queryStmt = $pdo->prepare("SELECT * FROM customers WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset"); +$queryStmt->execute($params); +$items = $queryStmt->fetchAll(); + +require __DIR__ . '/includes/header.php'; +?> + +
+
+
+

+

+
+ +
+ +
+
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ID
+ + +
+
+ + 1): ?> + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/healthz.php b/healthz.php new file mode 100644 index 0000000..15989d4 --- /dev/null +++ b/healthz.php @@ -0,0 +1,8 @@ + 'ok', + 'time' => date(DATE_ATOM), +], JSON_UNESCAPED_UNICODE); diff --git a/includes/app.php b/includes/app.php new file mode 100644 index 0000000..e804b31 --- /dev/null +++ b/includes/app.php @@ -0,0 +1,476 @@ + $type, 'message' => $message]; +} + +function pull_flash(): ?array +{ + $flash = $_SESSION['flash'] ?? null; + unset($_SESSION['flash']); + return $flash; +} + +function branches(): array +{ + return [ + 'muscat' => ['code' => 'muscat', 'name_ar' => 'فرع مسقط', 'name_en' => 'Muscat Branch', 'city_ar' => 'مسقط', 'city_en' => 'Muscat'], + 'sohar' => ['code' => 'sohar', 'name_ar' => 'فرع صحار', 'name_en' => 'Sohar Branch', 'city_ar' => 'صحار', 'city_en' => 'Sohar'], + 'nizwa' => ['code' => 'nizwa', 'name_ar' => 'فرع نزوى', 'name_en' => 'Nizwa Branch', 'city_ar' => 'نزوى', 'city_en' => 'Nizwa'], + ]; +} + +function branch_label(string $code): string +{ + $branch = branches()[$code] ?? null; + if (!$branch) { + return $code; + } + + return current_lang() === 'ar' ? $branch['name_ar'] : $branch['name_en']; +} + +function demo_users(): array +{ + return [ + 'owner' => [ + 'username' => 'owner', + 'password' => 'owner123', + 'role' => 'owner', + 'branch_code' => 'muscat', + 'name_ar' => 'مالك النظام', + 'name_en' => 'System Owner', + ], + 'manager_muscat' => [ + 'username' => 'manager_muscat', + 'password' => 'manager123', + 'role' => 'manager', + 'branch_code' => 'muscat', + 'name_ar' => 'مدير فرع مسقط', + 'name_en' => 'Muscat Branch Manager', + ], + 'cashier_sohar' => [ + 'username' => 'cashier_sohar', + 'password' => 'cashier123', + 'role' => 'cashier', + 'branch_code' => 'sohar', + 'name_ar' => 'كاشير فرع صحار', + 'name_en' => 'Sohar Cashier', + ], + ]; +} + +function role_label(string $role): string +{ + return match ($role) { + 'owner' => tr('مالك / مدير عام', 'Owner / Admin'), + 'manager' => tr('مدير فرع', 'Branch Manager'), + 'cashier' => tr('كاشير', 'Cashier'), + default => $role, + }; +} + +function current_user(): ?array +{ + return $_SESSION['auth_user'] ?? null; +} + +function login_attempt(string $username, string $password): bool +{ + $users = demo_users(); + if (!isset($users[$username])) { + return false; + } + + $user = $users[$username]; + if ($user['password'] !== $password) { + return false; + } + + $_SESSION['auth_user'] = $user; + return true; +} + +function logout_user(): void +{ + unset($_SESSION['auth_user']); +} + +function require_auth(): array +{ + $user = current_user(); + if (!$user) { + set_flash('warning', tr('يرجى تسجيل الدخول أولاً.', 'Please sign in first.')); + redirect_to('login.php'); + } + + return $user; +} + +function require_roles(array $roles): array +{ + $user = require_auth(); + if (!in_array($user['role'], $roles, true)) { + set_flash('warning', tr('ليس لديك صلاحية للوصول إلى هذه الصفحة.', 'You do not have permission to access this page.')); + redirect_to('index.php'); + } + + return $user; +} + +function can_access_branch(string $branchCode): bool +{ + $user = current_user(); + if (!$user) { + return false; + } + + if ($user['role'] === 'owner') { + return true; + } + + return $user['branch_code'] === $branchCode; +} + +function catalog(): array +{ + return [ + 'baklava_box' => ['sku' => 'baklava_box', 'name_ar' => 'بقلاوة مشكلة', 'name_en' => 'Mixed Baklava Box', 'price' => 18.50, 'base_stock' => 72, 'unit_ar' => 'علبة', 'unit_en' => 'box'], + 'date_truffles' => ['sku' => 'date_truffles', 'name_ar' => 'ترافل التمر', 'name_en' => 'Date Truffles', 'price' => 9.25, 'base_stock' => 120, 'unit_ar' => 'علبة', 'unit_en' => 'box'], + 'saffron_maamoul' => ['sku' => 'saffron_maamoul', 'name_ar' => 'معمول الزعفران', 'name_en' => 'Saffron Maamoul', 'price' => 7.80, 'base_stock' => 88, 'unit_ar' => 'صندوق', 'unit_en' => 'pack'], + 'pistachio_bites' => ['sku' => 'pistachio_bites', 'name_ar' => 'لقيمات الفستق', 'name_en' => 'Pistachio Bites', 'price' => 11.40, 'base_stock' => 64, 'unit_ar' => 'علبة', 'unit_en' => 'box'], + 'halwa_classic' => ['sku' => 'halwa_classic', 'name_ar' => 'حلوى عمانية كلاسيك', 'name_en' => 'Classic Omani Halwa', 'price' => 6.20, 'base_stock' => 150, 'unit_ar' => 'عبوة', 'unit_en' => 'jar'], + 'gift_tin' => ['sku' => 'gift_tin', 'name_ar' => 'علبة هدايا فاخرة', 'name_en' => 'Premium Gift Tin', 'price' => 24.00, 'base_stock' => 36, 'unit_ar' => 'علبة', 'unit_en' => 'tin'], + ]; +} + +function product_label(string $sku): string +{ + $item = catalog()[$sku] ?? null; + if (!$item) { + return $sku; + } + + return current_lang() === 'ar' ? $item['name_ar'] : $item['name_en']; +} + +function currency(float $amount): string +{ + return number_format($amount, 2) . ' ' . tr('ر.ع', 'OMR'); +} + +function sale_mode_label(string $mode): string +{ + return $mode === 'normal' ? tr('بيع عادي', 'Normal Sale') : tr('بيع نقاط البيع', 'POS Sale'); +} + +function ensure_sales_table(): void +{ + $sql = "CREATE TABLE IF NOT EXISTS sales_orders ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + receipt_no VARCHAR(50) NOT NULL UNIQUE, + sale_mode VARCHAR(20) NOT NULL, + branch_code VARCHAR(30) NOT NULL, + cashier_username VARCHAR(60) NOT NULL, + cashier_name VARCHAR(120) NOT NULL, + role_name VARCHAR(40) NOT NULL, + customer_name VARCHAR(120) DEFAULT NULL, + payment_method VARCHAR(30) NOT NULL, + items_json LONGTEXT NOT NULL, + item_count INT UNSIGNED NOT NULL DEFAULT 0, + subtotal DECIMAL(10,2) NOT NULL DEFAULT 0, + total_amount DECIMAL(10,2) NOT NULL DEFAULT 0, + notes TEXT DEFAULT NULL, + sale_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_sale_mode (sale_mode), + INDEX idx_branch_code (branch_code), + INDEX idx_sale_date (sale_date) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; + + db()->exec($sql); +} + +function create_sale(array $data): int +{ + ensure_sales_table(); + + $stmt = db()->prepare('INSERT INTO sales_orders + (receipt_no, sale_mode, branch_code, cashier_username, cashier_name, role_name, customer_name, payment_method, items_json, item_count, subtotal, total_amount, notes, sale_date) + VALUES + (:receipt_no, :sale_mode, :branch_code, :cashier_username, :cashier_name, :role_name, :customer_name, :payment_method, :items_json, :item_count, :subtotal, :total_amount, :notes, NOW())'); + + $stmt->bindValue(':receipt_no', $data['receipt_no']); + $stmt->bindValue(':sale_mode', $data['sale_mode']); + $stmt->bindValue(':branch_code', $data['branch_code']); + $stmt->bindValue(':cashier_username', $data['cashier_username']); + $stmt->bindValue(':cashier_name', $data['cashier_name']); + $stmt->bindValue(':role_name', $data['role_name']); + $stmt->bindValue(':customer_name', $data['customer_name']); + $stmt->bindValue(':payment_method', $data['payment_method']); + $stmt->bindValue(':items_json', json_encode($data['items'], JSON_UNESCAPED_UNICODE)); + $stmt->bindValue(':item_count', $data['item_count'], PDO::PARAM_INT); + $stmt->bindValue(':subtotal', $data['subtotal']); + $stmt->bindValue(':total_amount', $data['total_amount']); + $stmt->bindValue(':notes', $data['notes']); + $stmt->execute(); + + return (int) db()->lastInsertId(); +} + +function base_sales_query_filters(array &$params, ?string $mode = null, ?string $branch = null): string +{ + $sql = ' WHERE 1=1 '; + if ($mode) { + $sql .= ' AND sale_mode = :sale_mode '; + $params[':sale_mode'] = $mode; + } + if ($branch) { + $sql .= ' AND branch_code = :branch_code '; + $params[':branch_code'] = $branch; + } + + $user = current_user(); + if ($user && $user['role'] !== 'owner') { + $sql .= ' AND branch_code = :viewer_branch '; + $params[':viewer_branch'] = $user['branch_code']; + } + + return $sql; +} + +function fetch_sales(?string $mode = null, ?string $branch = null, int $limit = 50): array +{ + ensure_sales_table(); + $params = []; + $sql = 'SELECT * FROM sales_orders' . base_sales_query_filters($params, $mode, $branch) . ' ORDER BY sale_date DESC LIMIT :limit'; + $stmt = db()->prepare($sql); + foreach ($params as $key => $value) { + $stmt->bindValue($key, $value); + } + $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); + $stmt->execute(); + $rows = $stmt->fetchAll(); + + foreach ($rows as &$row) { + $row['items'] = json_decode((string) $row['items_json'], true) ?: []; + } + + return $rows; +} + +function fetch_sale(int $id): ?array +{ + ensure_sales_table(); + $stmt = db()->prepare('SELECT * FROM sales_orders WHERE id = :id LIMIT 1'); + $stmt->bindValue(':id', $id, PDO::PARAM_INT); + $stmt->execute(); + $sale = $stmt->fetch(); + + if (!$sale) { + return null; + } + + if (!can_access_branch((string) $sale['branch_code'])) { + return null; + } + + $sale['items'] = json_decode((string) $sale['items_json'], true) ?: []; + return $sale; +} + +function fetch_all_sales_for_scope(): array +{ + ensure_sales_table(); + $params = []; + $sql = 'SELECT * FROM sales_orders' . base_sales_query_filters($params); + $stmt = db()->prepare($sql); + foreach ($params as $key => $value) { + $stmt->bindValue($key, $value); + } + $stmt->execute(); + $rows = $stmt->fetchAll(); + foreach ($rows as &$row) { + $row['items'] = json_decode((string) $row['items_json'], true) ?: []; + } + return $rows; +} + +function dashboard_metrics(): array +{ + $sales = fetch_all_sales_for_scope(); + $today = date('Y-m-d'); + $todaySales = 0; + $todayRevenue = 0.0; + $normalCount = 0; + $posCount = 0; + + foreach ($sales as $sale) { + if (str_starts_with((string) $sale['sale_date'], $today)) { + $todaySales++; + $todayRevenue += (float) $sale['total_amount']; + } + if (($sale['sale_mode'] ?? '') === 'normal') { + $normalCount++; + } else { + $posCount++; + } + } + + return [ + 'today_sales' => $todaySales, + 'today_revenue' => $todayRevenue, + 'pos_count' => $posCount, + 'normal_count' => $normalCount, + 'recent' => array_slice(fetch_sales(null, null, 6), 0, 6), + ]; +} + +function report_metrics(): array +{ + $sales = fetch_all_sales_for_scope(); + $branchTotals = []; + $paymentTotals = []; + $productTotals = []; + $gross = 0.0; + + foreach ($sales as $sale) { + $branch = $sale['branch_code']; + $branchTotals[$branch] = ($branchTotals[$branch] ?? 0.0) + (float) $sale['total_amount']; + $payment = $sale['payment_method']; + $paymentTotals[$payment] = ($paymentTotals[$payment] ?? 0.0) + (float) $sale['total_amount']; + $gross += (float) $sale['total_amount']; + foreach ($sale['items'] as $item) { + $sku = (string) ($item['sku'] ?? ''); + $qty = (int) ($item['qty'] ?? 0); + $productTotals[$sku] = ($productTotals[$sku] ?? 0) + $qty; + } + } + + arsort($branchTotals); + arsort($paymentTotals); + arsort($productTotals); + + return [ + 'gross' => $gross, + 'branch_totals' => $branchTotals, + 'payment_totals' => $paymentTotals, + 'product_totals' => $productTotals, + 'sales_count' => count($sales), + ]; +} + +function stock_snapshot(): array +{ + $catalog = catalog(); + $sold = []; + foreach (fetch_all_sales_for_scope() as $sale) { + foreach ($sale['items'] as $item) { + $sku = (string) ($item['sku'] ?? ''); + $sold[$sku] = ($sold[$sku] ?? 0) + (int) ($item['qty'] ?? 0); + } + } + + $rows = []; + foreach ($catalog as $sku => $item) { + $base = (int) $item['base_stock']; + $used = $sold[$sku] ?? 0; + $rows[] = [ + 'sku' => $sku, + 'name' => current_lang() === 'ar' ? $item['name_ar'] : $item['name_en'], + 'base_stock' => $base, + 'sold' => $used, + 'available' => max(0, $base - $used), + 'price' => (float) $item['price'], + ]; + } + + usort($rows, static fn(array $a, array $b): int => $a['available'] <=> $b['available']); + return $rows; +} + +function module_cards(): array +{ + return [ + ['title_ar' => 'نقاط البيع', 'title_en' => 'POS Sale', 'path' => 'pos.php', 'desc_ar' => 'إتمام البيع السريع مع تحديث السجل.', 'desc_en' => 'Fast checkout with instant sales logging.'], + ['title_ar' => 'بيع عادي', 'title_en' => 'Normal Sale', 'path' => 'normal_sale.php', 'desc_ar' => 'فاتورة يدوية مع العميل والملاحظات.', 'desc_en' => 'Manual invoice flow with customer details and notes.'], + ['title_ar' => 'المبيعات', 'title_en' => 'Sales Ledger', 'path' => 'sales.php', 'desc_ar' => 'قائمة الفواتير مع التفاصيل والفرز.', 'desc_en' => 'Invoice list with filters and detail views.'], + ['title_ar' => 'المخزون', 'title_en' => 'Stock', 'path' => 'stock.php', 'desc_ar' => 'قراءة فورية للمخزون الحالي والتنبيهات.', 'desc_en' => 'Live stock snapshot and low-stock indicators.'], + ['title_ar' => 'المشتريات', 'title_en' => 'Purchases', 'path' => 'purchases.php', 'desc_ar' => 'واجهة مبدئية لاستلام الموردين بين الفروع.', 'desc_en' => 'Starter receiving board for suppliers and branches.'], + ['title_ar' => 'التقارير', 'title_en' => 'Reports', 'path' => 'reports.php', 'desc_ar' => 'مبيعات اليوم، الفروع، وأفضل الأصناف.', 'desc_en' => 'Daily sales, branch totals, and best sellers.'], + ['title_ar' => 'المستخدمون والأدوار', 'title_en' => 'Users & Roles', 'path' => 'users.php', 'desc_ar' => 'صلاحيات منفصلة للمالك والمدير والكاشير.', 'desc_en' => 'Separate access for owner, manager, and cashier.'], + ]; +} + +function purchase_pipeline(): array +{ + return [ + ['supplier' => 'Oman Dates Co.', 'reference' => 'PO-24019', 'branch' => 'muscat', 'status' => tr('بانتظار الاستلام', 'Pending Receiving'), 'eta' => '2026-04-20'], + ['supplier' => 'Golden Nuts', 'reference' => 'PO-24023', 'branch' => 'sohar', 'status' => tr('في الطريق', 'In Transit'), 'eta' => '2026-04-21'], + ['supplier' => 'Saffron House', 'reference' => 'PO-24026', 'branch' => 'nizwa', 'status' => tr('جاهز للفحص', 'Ready for QC'), 'eta' => '2026-04-22'], + ]; +} + +function receipt_code(): string +{ + return 'AR-' . date('ymd-His') . '-' . random_int(100, 999); +} diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..abaaa98 --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/includes/header.php b/includes/header.php new file mode 100644 index 0000000..3010f8a --- /dev/null +++ b/includes/header.php @@ -0,0 +1,157 @@ + + + + + + + <?= h($pageTitle) ?> · <?= h($projectName) ?> + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + + + +
+ + +
+ + + + + \ No newline at end of file diff --git a/includes/sale_form.php b/includes/sale_form.php new file mode 100644 index 0000000..44b60cb --- /dev/null +++ b/includes/sale_form.php @@ -0,0 +1,180 @@ + $sku, + 'name_ar' => $product['name_ar'], + 'name_en' => $product['name_en'], + 'qty' => $qty, + 'price' => $price, + 'line_total' => $lineTotal, + ]; + $subtotal += $lineTotal; + $itemCount += $qty; + } + + if ($normalized === []) { + $error = tr('السلة غير صالحة بعد التحقق من الأصناف.', 'The cart is invalid after product validation.'); + } else { + $cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en']; + $saleId = create_sale([ + 'receipt_no' => receipt_code(), + 'sale_mode' => $saleMode, + 'branch_code' => $branchCode, + 'cashier_username' => $user['username'], + 'cashier_name' => $cashierName, + 'role_name' => $user['role'], + 'customer_name' => $customerName !== '' ? $customerName : null, + 'payment_method' => $paymentMethod, + 'items' => $normalized, + 'item_count' => $itemCount, + 'subtotal' => $subtotal, + 'total_amount' => $subtotal, + 'notes' => $notes !== '' ? $notes : null, + ]); + + set_flash('success', $saleMode === 'normal' + ? tr('تم حفظ البيع العادي بنجاح.', 'Normal sale saved successfully.') + : tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.')); + redirect_to('sale.php', ['id' => $saleId]); + } + } +} + +require __DIR__ . '/header.php'; +?> +
+
+
+
+
+

+
+
+ +
+ +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+

+ +
+
+ +
+ +
+ +
+
+
+
+
+
+
+
+

+ 0 +
+
+

+

+
+
+
+
0.00
+
0.00
+
+
+ + +
+
+ +
+
+
+
+ + diff --git a/index.php b/index.php index 7205f3d..6fd54fc 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,224 @@ 0, 'today_revenue' => 0.0, 'pos_count' => 0, 'normal_count' => 0, 'recent' => []]; +try { + $metrics = dashboard_metrics(); +} catch (Throwable $e) { + $dbError = $e->getMessage(); +} +require __DIR__ . '/includes/header.php'; +?> -$phpVersion = PHP_VERSION; -$now = date('Y-m-d H:i:s'); -?> - - - - - - New Style - - - - - - - - - - - - - - - - - - - - - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ +
+
+
+

-
-
- Page updated: (UTC) -
- - + +
+ + + +
+ + + +
+
+
+
+
+
+ +
+

+
+
+
+ +
+
+
+
+
+ +
+

+
+
+
+ +
+
+
+
+
POS
+ +
+

+
+
+
+ +
+
+
+
+
+ +
+

+
+
+
+
+ +
+ +
+
+
+
+ +
+
+ +
+ +
+

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ + + + + + + + + +
+ +
+ +
+
+ +
+

+ - +
+
+ +
+

+ +
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/login.php b/login.php new file mode 100644 index 0000000..e673e2b --- /dev/null +++ b/login.php @@ -0,0 +1,110 @@ + + + + + + + <?= h(tr('تسجيل الدخول', 'Sign in')) ?> · <?= h($projectName) ?> + + + + + + + + + + + + + + + +
+
+
+
+
+

+

+
+
3
+
3
+
2
+
+
+
+
+
+
+
+
+
+
+

+
+ AR + EN +
+
+ +
+ +
+
+ + +
+
+ + +
+ +
+
+
+ + + +
+
+
+
+
+ + + + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..e5eea45 --- /dev/null +++ b/logout.php @@ -0,0 +1,5 @@ + $row) { + if ( + str_contains(mb_strtolower($row['supplier']), $lowerSearch) || + str_contains(mb_strtolower($row['reference']), $lowerSearch) + ) { + $filteredPurchases[$key] = $row; + } + } +} else { + $filteredPurchases = $allPurchases; +} + +// Pagination logic +$page = max(1, (int)($_GET['p'] ?? 1)); +$limit = 10; +$total = count($filteredPurchases); +$totalPages = max(1, ceil($total / $limit)); +$offset = ($page - 1) * $limit; +$purchaseRows = array_slice($filteredPurchases, $offset, $limit, true); + +require __DIR__ . '/includes/header.php'; +?> +
+
+
+

+

+
+
+ +
+
+ +
+
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + 1): ?> + + +
+ + + + \ No newline at end of file diff --git a/reports.php b/reports.php new file mode 100644 index 0000000..f728ed6 --- /dev/null +++ b/reports.php @@ -0,0 +1,52 @@ + 0.0, 'branch_totals' => [], 'payment_totals' => [], 'product_totals' => [], 'sales_count' => 0]; +try { + $report = report_metrics(); +} catch (Throwable $e) { + $dbError = $e->getMessage(); +} +require __DIR__ . '/includes/header.php'; +?> +
+
+
+
+
+
+
+
+

+ +
+ +

+ +
+ $amount): ?> +
+ +
+ +
+
+
+
+

+ +

+ +
+ $amount): ?> +
+ +
+ +
+
+
+ diff --git a/sale.php b/sale.php new file mode 100644 index 0000000..7258b95 --- /dev/null +++ b/sale.php @@ -0,0 +1,72 @@ + 0) { + try { + $sale = fetch_sale($id); + } catch (Throwable $e) { + $dbError = $e->getMessage(); + } +} +require __DIR__ . '/includes/header.php'; +?> +
+ +
+ +
+

+

+ +
+ +
+
+
+

+
·
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+ diff --git a/sales.php b/sales.php new file mode 100644 index 0000000..26ea230 --- /dev/null +++ b/sales.php @@ -0,0 +1,191 @@ +prepare($countSql); + foreach ($params as $key => $value) { + $countStmt->bindValue($key, $value); + } + $countStmt->execute(); + $total = $countStmt->fetchColumn(); + $totalPages = max(1, ceil($total / $limit)); + + // Fetch Data + $sql = 'SELECT * FROM sales_orders' . $where . ' ORDER BY sale_date DESC LIMIT :limit OFFSET :offset'; + $stmt = db()->prepare($sql); + foreach ($params as $key => $value) { + $stmt->bindValue($key, $value); + } + $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); + $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); + $stmt->execute(); + $sales = $stmt->fetchAll(); + +} catch (Throwable $e) { + $dbError = $e->getMessage(); +} + +require __DIR__ . '/includes/header.php'; +?> +
+
+
+

+
+
+
+ + POS + +
+
+ +
+ + + +
+ + +
+
+ + +
+ +
+

+

+
+ POS + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + +
+
+ + 1): ?> + + + +
+ + + + \ No newline at end of file diff --git a/stock.php b/stock.php new file mode 100644 index 0000000..0ef05f5 --- /dev/null +++ b/stock.php @@ -0,0 +1,276 @@ +getMessage(); +} + +$categories = []; +$suppliers = []; +try { + $pdo = db(); + $categories = $pdo->query('SELECT id, name_ar, name_en FROM categories ORDER BY name_ar ASC')->fetchAll(); + $suppliers = $pdo->query('SELECT id, name FROM suppliers ORDER BY name ASC')->fetchAll(); +} catch (Throwable $e) { + // Ignore if not present +} + +// Search logic +$search = $_GET['q'] ?? ''; +$filteredStock = []; +if ($search && empty($dbError)) { + $lowerSearch = mb_strtolower($search); + foreach ($allStock as $key => $row) { + if ( + str_contains(mb_strtolower($row['sku']), $lowerSearch) || + str_contains(mb_strtolower($row['name']), $lowerSearch) + ) { + $filteredStock[$key] = $row; + } + } +} else { + $filteredStock = $allStock; +} + +// Pagination logic +$page = max(1, (int)($_GET['p'] ?? 1)); +$limit = 10; +$total = count($filteredStock); +$totalPages = max(1, ceil($total / $limit)); +$offset = ($page - 1) * $limit; +$stockRows = array_slice($filteredStock, $offset, $limit, true); + +require __DIR__ . '/includes/header.php'; +?> + +
+
+
+

+

+
+
+ +
+
+ +
+
+ + +
+
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SKU
+ + + + + + + + +
+
+ + 1): ?> + + + +
+ + + + + + + diff --git a/suppliers.php b/suppliers.php new file mode 100644 index 0000000..7330c8c --- /dev/null +++ b/suppliers.php @@ -0,0 +1,251 @@ +prepare('INSERT INTO suppliers (name, contact_person, phone, email, address) VALUES (?, ?, ?, ?, ?)'); + $stmt->execute([$_POST['name'], $_POST['contact_person'] ?? '', $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '']); + set_flash('success', tr('تمت إضافة المورد بنجاح', 'Supplier added successfully')); + redirect_to('suppliers.php'); + } elseif ($action === 'edit') { + $stmt = $pdo->prepare('UPDATE suppliers SET name = ?, contact_person = ?, phone = ?, email = ?, address = ? WHERE id = ?'); + $stmt->execute([$_POST['name'], $_POST['contact_person'] ?? '', $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '', $_POST['id']]); + set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully')); + redirect_to('suppliers.php'); + } elseif ($action === 'delete') { + $stmt = $pdo->prepare('DELETE FROM suppliers WHERE id = ?'); + $stmt->execute([$_POST['id']]); + set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully')); + redirect_to('suppliers.php'); + } +} + +// Pagination & Search +$page = max(1, (int)($_GET['p'] ?? 1)); +$limit = 10; +$offset = ($page - 1) * $limit; +$search = $_GET['q'] ?? ''; + +$where = '1=1'; +$params = []; +if ($search) { + $where .= ' AND (name LIKE ? OR phone LIKE ? OR email LIKE ?)'; + $params[] = "%$search%"; + $params[] = "%$search%"; + $params[] = "%$search%"; +} + +$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM suppliers WHERE $where"); +$totalStmt->execute($params); +$total = $totalStmt->fetchColumn(); +$totalPages = ceil($total / $limit); + +$queryStmt = $pdo->prepare("SELECT * FROM suppliers WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset"); +$queryStmt->execute($params); +$items = $queryStmt->fetchAll(); + +require __DIR__ . '/includes/header.php'; +?> + +
+
+
+

+

+
+ +
+ +
+
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ID
+ + +
+
+ + 1): ?> + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/users.php b/users.php new file mode 100644 index 0000000..46c116b --- /dev/null +++ b/users.php @@ -0,0 +1,203 @@ + $acc) { + if ( + str_contains(mb_strtolower($acc['name_ar']), $lowerSearch) || + str_contains(mb_strtolower($acc['name_en']), $lowerSearch) || + str_contains(mb_strtolower($acc['username']), $lowerSearch) + ) { + $filteredAccounts[$key] = $acc; + } + } +} else { + $filteredAccounts = $allAccounts; +} + +// Pagination logic +$page = max(1, (int)($_GET['p'] ?? 1)); +$limit = 10; +$total = count($filteredAccounts); +$totalPages = max(1, ceil($total / $limit)); +$offset = ($page - 1) * $limit; +$accounts = array_slice($filteredAccounts, $offset, $limit, true); + +require __DIR__ . '/includes/header.php'; +?> +
+
+
+

+

+
+ +
+ +
+
+ + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + $account): ?> + + + + + + + + + + + +
POS
+
+
+
+ + +
+
+ + 1): ?> + + +
+ + + + + + + \ No newline at end of file