diff --git a/admin/categories.php b/admin/categories.php index 07ce466..79f760c 100644 --- a/admin/categories.php +++ b/admin/categories.php @@ -81,12 +81,12 @@ include 'includes/header.php';
-

Product Categories

+

Organize your menu and inventory

@@ -96,7 +96,7 @@ include 'includes/header.php';
-

No categories found

+

Start by adding your first category.

@@ -105,10 +105,10 @@ include 'includes/header.php'; - - - - + + + + @@ -124,18 +124,18 @@ include 'includes/header.php';
@@ -156,7 +156,7 @@ include 'includes/header.php'; @@ -205,7 +210,7 @@ include 'includes/header.php'; - \ No newline at end of file + diff --git a/admin/includes/header.php b/admin/includes/header.php index 66e8ad5..e8d8504 100644 --- a/admin/includes/header.php +++ b/admin/includes/header.php @@ -10,6 +10,29 @@ if (file_exists(__DIR__ . '/../../db/config.php')) { if (file_exists(__DIR__ . '/../../includes/functions.php')) { require_once __DIR__ . '/../../includes/functions.php'; } +if (file_exists(__DIR__ . '/../../includes/lang.php')) { + require_once __DIR__ . '/../../includes/lang.php'; +} + +// Handle language switching +if (isset($_GET['lang'])) { + $allowed_langs = ['en', 'ar']; + if (in_array($_GET['lang'], $allowed_langs)) { + $_SESSION['lang'] = $_GET['lang']; + } + // Remove lang from URL to prevent infinite redirect or messy URLs + $current_url = strtok($_SERVER["REQUEST_URI"], '?'); + $query = $_GET; + unset($query['lang']); + if (count($query) > 0) { + $current_url .= '?' . http_build_query($query); + } + header("Location: $current_url"); + exit; +} + +$currentLang = $_SESSION['lang'] ?? 'en'; +$isRTL = ($currentLang === 'ar'); // Require login for all admin pages if (function_exists('require_login')) { @@ -57,7 +80,7 @@ function can_view($module) { } ?> - + @@ -69,13 +92,13 @@ function can_view($module) { - + - + \ No newline at end of file diff --git a/admin/profile.php b/admin/profile.php index 772aae7..fd76caa 100644 --- a/admin/profile.php +++ b/admin/profile.php @@ -94,7 +94,7 @@ include 'includes/header.php'; ?>
-

My Profile

+

Manage your personal information and account settings.

@@ -107,17 +107,22 @@ include 'includes/header.php';
- - + +
- -
- - -
+ +
@@ -127,7 +132,7 @@ include 'includes/header.php';
- +
@@ -170,7 +175,7 @@ include 'includes/header.php';
- +
@@ -216,34 +221,38 @@ include 'includes/header.php'; - \ No newline at end of file + diff --git a/api/order.php b/api/order.php index bbcc8dd..f92ef9a 100644 --- a/api/order.php +++ b/api/order.php @@ -23,7 +23,7 @@ try { : 'dine-in'; // Get outlet_id from input, default to 1 if missing - $outlet_id = isset($data['outlet_id']) ? intval($data['outlet_id']) : 1; + $outlet_id = !empty($data['outlet_id']) ? intval($data['outlet_id']) : 1; $table_id = null; $table_number = null; @@ -66,7 +66,7 @@ try { } // Customer Handling - $customer_id = $data['customer_id'] ?? null; + $customer_id = !empty($data['customer_id']) ? intval($data['customer_id']) : null; $customer_name = $data['customer_name'] ?? null; $customer_phone = $data['customer_phone'] ?? null; @@ -137,7 +137,7 @@ try { // User/Payment info $user = get_logged_user(); $user_id = $user ? $user['id'] : null; - $payment_type_id = isset($data['payment_type_id']) ? intval($data['payment_type_id']) : null; + $payment_type_id = !empty($data['payment_type_id']) ? intval($data['payment_type_id']) : null; // VAT vs Discount: We repurpose the 'discount' column in the database to store VAT value. // If it's a positive value, it's VAT. If negative, it acts as a discount (loyalty). diff --git a/assets/js/main.js b/assets/js/main.js index 915c9a7..4a595d9 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -89,6 +89,9 @@ document.addEventListener('DOMContentLoaded', () => { let currentCategory = 'all'; let currentSearchQuery = ''; + // Translation helper check + const _t = (key) => (typeof t === 'function') ? t(key) : key; + function formatCurrency(amount) { const symbol = settings.currency_symbol || '$'; const decimals = parseInt(settings.currency_decimals || 2); @@ -100,8 +103,9 @@ document.addEventListener('DOMContentLoaded', () => { items.forEach(item => { const matchesCategory = (currentCategory == 'all' || item.dataset.category == currentCategory); const name = (item.dataset.name || '').toLowerCase(); + const name_ar = (item.dataset.nameAr || '').toLowerCase(); const sku = (item.dataset.sku || '').toLowerCase(); - const matchesSearch = name.includes(currentSearchQuery) || sku.includes(currentSearchQuery); + const matchesSearch = name.includes(currentSearchQuery) || name_ar.includes(currentSearchQuery) || sku.includes(currentSearchQuery); item.style.display = (matchesCategory && matchesSearch) ? 'block' : 'none'; }); @@ -165,11 +169,11 @@ document.addEventListener('DOMContentLoaded', () => { recallList.appendChild(item); }); } else { - recallList.innerHTML = '
No unpaid bills found.
'; + recallList.innerHTML = `
${_t('none')}
`; } }) .catch(() => { - recallList.innerHTML = '
Error fetching orders.
'; + recallList.innerHTML = `
${_t('error')}
`; }); } @@ -196,7 +200,7 @@ document.addEventListener('DOMContentLoaded', () => { if (recallModal) recallModal.hide(); showToast(`Order #${orderId} loaded!`, 'success'); } else { - showToast(data.error || 'Failed to load order', 'danger'); + showToast(data.error || _t('error'), 'danger'); } }); } @@ -230,7 +234,7 @@ document.addEventListener('DOMContentLoaded', () => { }); customerResults.style.display = 'block'; } else { - customerResults.innerHTML = '
No results found
'; + customerResults.innerHTML = `
${_t('none')}
`; customerResults.style.display = 'block'; } }); @@ -316,14 +320,15 @@ document.addEventListener('DOMContentLoaded', () => { if (!variantSelectionModal) return; const list = document.getElementById('variant-list'); const title = document.getElementById('variantModalTitle'); - if (title) title.textContent = `Option: ${product.name}`; + if (title) title.textContent = `${_t('variant')}: ${LANG === 'ar' && product.name_ar ? product.name_ar : product.name}`; if (!list) return; list.innerHTML = ''; variants.forEach(v => { const btn = document.createElement('button'); btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center'; const finalPrice = parseFloat(product.price) + parseFloat(v.price_adjustment); - btn.innerHTML = `${v.name}${formatCurrency(finalPrice)}`; + const vName = (LANG === 'ar' && v.name_ar) ? v.name_ar : v.name; + btn.innerHTML = `${vName}${formatCurrency(finalPrice)}`; btn.onclick = () => { addToCart({ id: product.id, name: product.name, name_ar: product.name_ar || "", @@ -369,11 +374,11 @@ document.addEventListener('DOMContentLoaded', () => { function updateCart() { if (!cartItemsContainer) return; if (cart.length === 0) { - cartItemsContainer.innerHTML = '

Cart is empty

'; + cartItemsContainer.innerHTML = `

${_t('cart_empty')}

`; if (cartSubtotal) cartSubtotal.innerText = formatCurrency(0); if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(0); - if (quickOrderBtn) quickOrderBtn.disabled = true; - if (placeOrderBtn) placeOrderBtn.disabled = true; + if (quickOrderBtn) { quickOrderBtn.disabled = true; quickOrderBtn.innerText = _t('quick_pay'); } + if (placeOrderBtn) { placeOrderBtn.disabled = true; placeOrderBtn.innerText = _t('save_bill'); } return; } @@ -384,10 +389,13 @@ document.addEventListener('DOMContentLoaded', () => { subtotal += itemTotal; const row = document.createElement('div'); row.className = 'd-flex justify-content-between align-items-center mb-3 border-bottom pb-2'; + const itemName = (LANG === 'ar' && item.name_ar) ? item.name_ar : item.name; + const otherName = (LANG === 'ar') ? item.name : item.name_ar; + row.innerHTML = `
-
${item.name}
- ${item.name_ar ? `
${item.name_ar}
` : ''} +
${itemName}
+ ${otherName ? `
${otherName}
` : ''}
${formatCurrency(item.price)}
@@ -397,7 +405,7 @@ document.addEventListener('DOMContentLoaded', () => {
${formatCurrency(itemTotal)}
- +
`; cartItemsContainer.appendChild(row); @@ -409,8 +417,8 @@ document.addEventListener('DOMContentLoaded', () => { if (cartVatInput) cartVatInput.value = vat.toFixed(2); const total = subtotal + vat; if (cartTotalPrice) cartTotalPrice.innerText = formatCurrency(total); - if (quickOrderBtn) quickOrderBtn.disabled = false; - if (placeOrderBtn) placeOrderBtn.disabled = false; + if (quickOrderBtn) { quickOrderBtn.disabled = false; quickOrderBtn.innerText = _t('quick_pay'); } + if (placeOrderBtn) { placeOrderBtn.disabled = false; placeOrderBtn.innerText = _t('save_bill'); } } if (quickOrderBtn) { @@ -463,7 +471,7 @@ document.addEventListener('DOMContentLoaded', () => { .then(res => res.json()) .then(data => { if (data.success) { - showToast(`Order #${data.order_id} placed!`, 'success'); + showToast(`${_t('order_placed')} #${data.order_id}`, 'success'); clearCart(); if (paymentSelectionModal) paymentSelectionModal.hide(); if (clearCustomerBtn) clearCustomerBtn.click(); diff --git a/assets/pasted-20260224-165424-865370bd.png b/assets/pasted-20260224-165424-865370bd.png new file mode 100644 index 0000000..659a5da Binary files /dev/null and b/assets/pasted-20260224-165424-865370bd.png differ diff --git a/includes/lang.php b/includes/lang.php new file mode 100644 index 0000000..53136ef --- /dev/null +++ b/includes/lang.php @@ -0,0 +1,202 @@ + [ + 'dashboard' => 'Dashboard', + 'pos_operations' => 'POS & Operations', + 'pos_terminal' => 'POS Terminal', + 'orders_pos' => 'Orders (POS)', + 'kitchen_view' => 'Kitchen View', + 'ads_management' => 'Ads Management', + 'menu_management' => 'Menu Management', + 'products' => 'Products', + 'categories' => 'Categories', + 'restaurant_setup' => 'Restaurant Setup', + 'outlets' => 'Outlets', + 'areas' => 'Areas', + 'tables' => 'Tables', + 'people_partners' => 'People & Partners', + 'customers' => 'Customers', + 'suppliers' => 'Suppliers', + 'loyalty' => 'Loyalty', + 'financials' => 'Financials', + 'purchases' => 'Purchases', + 'expenses' => 'Expenses', + 'expense_categories' => 'Expense Categories', + 'reports_analytics' => 'Reports & Analytics', + 'daily_reports' => 'Daily Reports', + 'user_management' => 'User Management', + 'users' => 'Users', + 'roles_groups' => 'Roles / Groups', + 'attendance' => 'Attendance', + 'staff_ratings' => 'Staff Ratings', + 'settings' => 'Settings', + 'payment_types' => 'Payment Types', + 'integrations' => 'Integrations', + 'company' => 'Company', + 'backup_restore' => 'Backup & Restore', + 'view_site' => 'View Site', + 'theme' => 'Theme', + 'language' => 'Language', + 'logout' => 'Logout', + 'my_profile' => 'My Profile', + 'company_settings' => 'Company Settings', + 'signed_in_as' => 'Signed in as', + 'welcome_back' => 'Welcome back', + 'select_theme' => 'Select Theme', + 'select_language' => 'Select Language', + 'default' => 'Default', + 'dark' => 'Dark', + 'ocean' => 'Ocean', + 'forest' => 'Forest', + 'grape' => 'Grape', + 'english' => 'English', + 'arabic' => 'Arabic', + 'search' => 'Search', + 'filter' => 'Filter', + 'add' => 'Add', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'save' => 'Save', + 'cancel' => 'Cancel', + 'close' => 'Close', + 'actions' => 'Actions', + 'name' => 'Name', + 'arabic_name' => 'Arabic Name', + 'description' => 'Description', + 'status' => 'Status', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'all' => 'All', + 'none' => 'None', + 'price' => 'Price', + 'quantity' => 'Quantity', + 'total' => 'Total', + 'date' => 'Date', + 'customer' => 'Customer', + 'phone' => 'Phone', + 'email' => 'Email', + 'address' => 'Address', + 'points' => 'Points', + 'stock' => 'Stock', + 'category' => 'Category', + 'variant' => 'Variant', + 'table' => 'Table', + 'outlet' => 'Outlet', + 'order_no' => 'Order No', + 'payment' => 'Payment', + 'type' => 'Type', + 'commission' => 'Commission', + 'preparing' => 'Preparing', + 'ready' => 'Ready', + 'completed' => 'Completed', + 'cancelled' => 'Cancelled', + 'takeaway' => 'Takeaway', + 'dine_in' => 'Dine-In', + 'delivery' => 'Delivery', + 'auto_translate' => 'Auto-translate', + ], + 'ar' => [ + 'dashboard' => 'لوحة القيادة', + 'pos_operations' => 'نقطة البيع والعمليات', + 'pos_terminal' => 'جهاز نقطة البيع', + 'orders_pos' => 'الطلبات (نقطة البيع)', + 'kitchen_view' => 'عرض المطبخ', + 'ads_management' => 'إدارة الإعلانات', + 'menu_management' => 'إدارة القائمة', + 'products' => 'المنتجات', + 'categories' => 'الفئات', + 'restaurant_setup' => 'إعداد المطعم', + 'outlets' => 'المنافذ', + 'areas' => 'المناطق', + 'tables' => 'الطاولات', + 'people_partners' => 'الأشخاص والشركاء', + 'customers' => 'العملاء', + 'suppliers' => 'الموردين', + 'loyalty' => 'الولاء', + 'financials' => 'المالية', + 'purchases' => 'المشتريات', + 'expenses' => 'المصاريف', + 'expense_categories' => 'فئات المصاريف', + 'reports_analytics' => 'التقارير والتحليلات', + 'daily_reports' => 'التقارير اليومية', + 'user_management' => 'إدارة المستخدمين', + 'users' => 'المستخدمين', + 'roles_groups' => 'الأدوار / المجموعات', + 'attendance' => 'الحضور', + 'staff_ratings' => 'تقييمات الموظفين', + 'settings' => 'الإعدادات', + 'payment_types' => 'أنواع الدفع', + 'integrations' => 'التكاملات', + 'company' => 'الشركة', + 'backup_restore' => 'النسخ الاحتياطي والاستعادة', + 'view_site' => 'عرض الموقع', + 'theme' => 'المظهر', + 'language' => 'اللغة', + 'logout' => 'تسجيل الخروج', + 'my_profile' => 'ملفي الشخصي', + 'company_settings' => 'إعدادات الشركة', + 'signed_in_as' => 'تم تسجيل الدخول بصفتك', + 'welcome_back' => 'مرحباً بعودتك', + 'select_theme' => 'اختر المظهر', + 'select_language' => 'اختر اللغة', + 'default' => 'الافتراضي', + 'dark' => 'داكن', + 'ocean' => 'محيط', + 'forest' => 'غابة', + 'grape' => 'عنب', + 'english' => 'الإنجليزية', + 'arabic' => 'العربية', + 'search' => 'بحث', + 'filter' => 'تصفية', + 'add' => 'إضافة', + 'edit' => 'تعديل', + 'delete' => 'حذف', + 'save' => 'حفظ', + 'cancel' => 'إلغاء', + 'close' => 'إغلاق', + 'actions' => 'إجراءات', + 'name' => 'الاسم', + 'arabic_name' => 'الاسم بالعربية', + 'description' => 'الوصف', + 'status' => 'الحالة', + 'active' => 'نشط', + 'inactive' => 'غير نشط', + 'all' => 'الكل', + 'none' => 'لا يوجد', + 'price' => 'السعر', + 'quantity' => 'الكمية', + 'total' => 'الإجمالي', + 'date' => 'التاريخ', + 'customer' => 'العميل', + 'phone' => 'الهاتف', + 'email' => 'البريد الإلكتروني', + 'address' => 'العنوان', + 'points' => 'النقاط', + 'stock' => 'المخزون', + 'category' => 'الفئة', + 'variant' => 'النوع', + 'table' => 'الطاولة', + 'outlet' => 'المنفذ', + 'order_no' => 'رقم الطلب', + 'payment' => 'الدفع', + 'type' => 'النوع', + 'commission' => 'العمولة', + 'preparing' => 'قيد التحضير', + 'ready' => 'جاهز', + 'completed' => 'مكتمل', + 'cancelled' => 'ملغي', + 'takeaway' => 'سفري', + 'dine_in' => 'محلي', + 'delivery' => 'توصيل', + 'auto_translate' => 'ترجمة تلقائية', + ] + ]; + + return $translations[$lang][$key] ?? $translations['en'][$key] ?? $key; +} diff --git a/pos.php b/pos.php index 481e50b..316a211 100644 --- a/pos.php +++ b/pos.php @@ -2,6 +2,30 @@ declare(strict_types=1); require_once __DIR__ . '/db/config.php'; require_once __DIR__ . '/includes/functions.php'; +require_once __DIR__ . '/includes/lang.php'; + +if (session_status() === PHP_SESSION_NONE) { + session_start(); +} + +// Handle language switching +if (isset($_GET['lang'])) { + $allowed_langs = ['en', 'ar']; + if (in_array($_GET['lang'], $allowed_langs)) { + $_SESSION['lang'] = $_GET['lang']; + } + $current_url = strtok($_SERVER["REQUEST_URI"], '?'); + $query = $_GET; + unset($query['lang']); + if (count($query) > 0) { + $current_url .= '?' . http_build_query($query); + } + header("Location: $current_url"); + exit; +} + +$currentLang = $_SESSION['lang'] ?? 'en'; +$isRTL = ($currentLang === 'ar'); require_permission('pos_view'); @@ -39,7 +63,7 @@ if (!has_permission('all')) { } $categories = $pdo->query("SELECT * FROM categories ORDER BY sort_order")->fetchAll(); -$all_products = $pdo->query("SELECT p.*, c.name as category_name FROM products p JOIN categories c ON p.category_id = c.id")->fetchAll(); +$all_products = $pdo->query("SELECT p.*, c.name as category_name, c.name_ar as category_name_ar FROM products p JOIN categories c ON p.category_id = c.id")->fetchAll(); $payment_types = $pdo->query("SELECT * FROM payment_types WHERE is_active = 1 ORDER BY id")->fetchAll(); // Fetch variants @@ -56,7 +80,7 @@ $order_type = $_GET['order_type'] ?? 'takeaway'; $current_outlet_name = 'Unknown Outlet'; foreach ($outlets as $o) { if ($o['id'] == $outlet_id) { - $current_outlet_name = $o['name']; + $current_outlet_name = ($isRTL && !empty($o['name_ar'])) ? $o['name_ar'] : $o['name']; break; } } @@ -69,7 +93,7 @@ if (!$loyalty_settings) { } ?> - + @@ -88,15 +112,15 @@ if (!$loyalty_settings) { body { height: 100vh; overflow: hidden; font-family: 'Inter', 'Noto Sans Arabic', sans-serif; background: #f4f7f6; } .scrollable-y { overflow-y: auto; height: 100%; scrollbar-width: thin; } .pos-layout { height: calc(100vh - 60px); } - .pos-categories { background: #fff; height: 100%; border-right: 1px solid #e0e0e0; } + .pos-categories { background: #fff; height: 100%; border-: 1px solid #e0e0e0; } .pos-products { background: #f8fafc; height: 100%; display: flex; flex-direction: column; } - .pos-cart { background: #fff; height: 100%; border-left: 1px solid #e0e0e0; display: flex; flex-direction: column; } + .pos-cart { background: #fff; height: 100%; border-: 1px solid #e0e0e0; display: flex; flex-direction: column; } .product-card { transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; border: 1px solid transparent !important; background: #fff; } .product-card:active { transform: scale(0.95); } .product-card:hover { border-color: #0d6efd !important; box-shadow: 0 4px 12px rgba(0,0,0,0.08) !important; } - .category-btn { text-align: left; border: none; background: none; padding: 10px 12px; width: 100%; display: flex; align-items: center; gap: 10px; border-radius: 12px; color: #64748b; font-weight: 700; transition: all 0.2s; } + .category-btn { text-align: ; border: none; background: none; padding: 10px 12px; width: 100%; display: flex; align-items: center; gap: 10px; border-radius: 12px; color: #64748b; font-weight: 700; transition: all 0.2s; } .category-btn:hover { background-color: #f1f5f9; color: #0f172a; } .category-btn.active { background-color: #0d6efd; color: white; box-shadow: 0 4px 6px -1px rgba(13, 110, 253, 0.3); } .search-dropdown { position: absolute; width: 100%; z-index: 1000; max-height: 200px; overflow-y: auto; display: none; } @@ -104,17 +128,13 @@ if (!$loyalty_settings) { /* Compact Card adjustments */ .card-img-container { height: 75px; position: relative; background: #f1f5f9; } .card-img-container img { height: 100%; width: 100%; object-fit: cover; transition: transform 0.3s; } - .product-card:hover .card-img-container img { transform: scale(1.05); } .product-title { font-size: 0.75rem; line-height: 1.2; height: 1.8rem; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; font-weight: 700; color: #1e293b; } .product-price-tag { font-size: 0.85rem; color: #0d6efd; font-weight: 700; } .product-cat-name { font-size: 0.65rem; color: #94a3b8; } - /* Custom Grid for 8 columns */ + /* Custom Grid for 7 columns */ @media (min-width: 1200px) { - .row-cols-xl-8 > * { flex: 0 0 auto; width: 12.5%; } - } - @media (min-width: 1400px) { - .row-cols-xxl-10 > * { flex: 0 0 auto; width: 10%; } + .row-cols-xl-7 > *, .row-cols-xxl-7 > * { flex: 0 0 auto; width: 14.285714%; } } @media (max-width: 576px) { @@ -132,6 +152,7 @@ if (!$loyalty_settings) { body { overflow: visible !important; height: auto !important; } } .print-only { display: none; } + .dropdown-menu { } @@ -150,21 +171,32 @@ if (!$loyalty_settings) {
+ + + 1): ?> - Kitchen - Orders - + + +
@@ -175,12 +207,12 @@ if (!$loyalty_settings) {
@@ -192,7 +224,7 @@ if (!$loyalty_settings) {
@@ -216,14 +248,14 @@ if (!$loyalty_settings) { - + 1): ?>
@@ -233,12 +265,13 @@ if (!$loyalty_settings) {
-
+
@@ -258,8 +291,10 @@ if (!$loyalty_settings) {
-
- +
+ +
+
@@ -277,19 +312,19 @@ if (!$loyalty_settings) {
> - + > - + > - +
- - + + - +
@@ -299,24 +334,24 @@ if (!$loyalty_settings) {
-

Cart is empty

+

- Subtotal +
- Total Payable +
- +
- - + +
@@ -329,7 +364,7 @@ if (!$loyalty_settings) {
CategoryArabic NameDescriptionActions
-
+
+ onclick='prepareEditForm()'> - Delete +