update pos

This commit is contained in:
Flatlogic Bot 2026-02-24 17:07:31 +00:00
parent 05a5289cfc
commit 6e5310e4dc
9 changed files with 591 additions and 254 deletions

View File

@ -81,12 +81,12 @@ include 'includes/header.php';
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-1">Product Categories</h2>
<h2 class="fw-bold mb-1"><?= t('categories') ?></h2>
<p class="text-muted mb-0">Organize your menu and inventory</p>
</div>
<?php if (has_permission('categories_add')): ?>
<button class="btn btn-primary btn-lg shadow-sm" data-bs-toggle="modal" data-bs-target="#categoryModal" onclick="prepareAddForm()" style="border-radius: 12px;">
<i class="bi bi-plus-lg me-1"></i> Add Category
<i class="bi bi-plus-lg me-1"></i> <?= t('add') ?> <?= t('categories') ?>
</button>
<?php endif; ?>
</div>
@ -96,7 +96,7 @@ include 'includes/header.php';
<?php if (empty($categories)): ?>
<div class="text-center py-5 bg-white rounded-4 shadow-sm">
<i class="bi bi-tags display-1 text-muted opacity-25 mb-3 d-block"></i>
<h4 class="text-dark">No categories found</h4>
<h4 class="text-dark"><?= t('none') ?></h4>
<p class="text-muted">Start by adding your first category.</p>
</div>
<?php else: ?>
@ -105,10 +105,10 @@ include 'includes/header.php';
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Category</th>
<th>Arabic Name</th>
<th>Description</th>
<th class="text-end pe-4">Actions</th>
<th class="ps-4"><?= t('category') ?></th>
<th><?= t('arabic_name') ?></th>
<th><?= t('description') ?></th>
<th class="text-end pe-4"><?= t('actions') ?></th>
</tr>
</thead>
<tbody>
@ -124,18 +124,18 @@ include 'includes/header.php';
<div class="text-dark"><?= htmlspecialchars($cat['name_ar'] ?? '-') ?></div>
</td>
<td>
<div class="text-muted small text-truncate" style="max-width: 300px;"><?= htmlspecialchars($cat['description'] ?? 'No description') ?></div>
<div class="text-muted small text-truncate" style="max-width: 300px;"><?= htmlspecialchars($cat['description'] ?? t('none')) ?></div>
</td>
<td class="text-end pe-4">
<div class="d-inline-flex gap-2">
<?php if (has_permission('categories_edit') || has_permission('categories_add')): ?>
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3"
data-bs-toggle="modal" data-bs-target="#categoryModal"
onclick='prepareEditForm(<?= htmlspecialchars(json_encode($cat), ENT_QUOTES, "UTF-8") ?>)'>Edit</button>
onclick='prepareEditForm(<?= htmlspecialchars(json_encode($cat), ENT_QUOTES, "UTF-8") ?>)'><?= t('edit') ?></button>
<?php endif; ?>
<?php if (has_permission('categories_del')): ?>
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Delete category? Ensure no products are linked.')">Delete</a>
<a href="?delete=<?= $cat['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Delete category? Ensure no products are linked.')"><?= t('delete') ?></a>
<?php endif; ?>
</div>
</td>
@ -156,7 +156,7 @@ include 'includes/header.php';
<div class="modal-dialog">
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header bg-primary text-white border-0 py-3">
<h5 class="modal-title fw-bold" id="categoryModalTitle">Add New Category</h5>
<h5 class="modal-title fw-bold" id="categoryModalTitle"><?= t('add') ?> <?= t('categories') ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" id="categoryForm" enctype="multipart/form-data">
@ -165,22 +165,27 @@ include 'includes/header.php';
<input type="hidden" name="id" id="categoryId">
<div class="mb-3">
<label class="form-label small fw-bold text-muted">CATEGORY NAME <span class="text-danger">*</span></label>
<div class="input-group">
<input type="text" name="name" id="categoryName" class="form-control rounded-start-3 border-0 bg-light" required>
<button class="btn btn-outline-secondary border-0 bg-light" type="button" id="btnTranslate">
<i class="bi bi-translate text-primary"></i>
</button>
</div>
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('name') ?> (EN) <span class="text-danger">*</span></span>
<a href="javascript:void(0)" onclick="translateTo('English')" class="text-decoration-none small text-primary fw-bold" id="translateBtnEn">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="name" id="categoryName" class="form-control rounded-3 border-0 bg-light" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold text-muted">ARABIC NAME</label>
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('arabic_name') ?></span>
<a href="javascript:void(0)" onclick="translateTo('Arabic')" class="text-decoration-none small text-primary fw-bold" id="translateBtnAr">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="name_ar" id="categoryNameAr" class="form-control rounded-3 border-0 bg-light" dir="rtl">
</div>
<div class="mb-3">
<label class="form-label small fw-bold text-muted">DESCRIPTION</label>
<label class="form-label small fw-bold text-muted"><?= t('description') ?></label>
<textarea name="description" id="categoryDescription" class="form-control rounded-3 border-0 bg-light" rows="3" placeholder="Optional category description..."></textarea>
</div>
@ -195,8 +200,8 @@ include 'includes/header.php';
</div>
</div>
<div class="modal-footer border-0 p-4 pt-0">
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold shadow-sm">Save Category</button>
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal"><?= t('cancel') ?></button>
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold shadow-sm"><?= t('save') ?></button>
</div>
</form>
</div>
@ -205,7 +210,7 @@ include 'includes/header.php';
<script>
function prepareAddForm() {
document.getElementById('categoryModalTitle').innerText = 'Add New Category';
document.getElementById('categoryModalTitle').innerText = '<?= t('add') ?> <?= t('categories') ?>';
document.getElementById('categoryAction').value = 'add_category';
document.getElementById('categoryForm').reset();
document.getElementById('categoryId').value = '';
@ -214,7 +219,7 @@ function prepareAddForm() {
function prepareEditForm(cat) {
if (!cat) return;
document.getElementById('categoryModalTitle').innerText = 'Edit Category: ' + cat.name;
document.getElementById('categoryModalTitle').innerText = '<?= t('edit') ?> <?= t('categories') ?>: ' + cat.name;
document.getElementById('categoryAction').value = 'edit_category';
document.getElementById('categoryId').value = cat.id;
document.getElementById('categoryName').value = cat.name;
@ -230,45 +235,42 @@ function prepareEditForm(cat) {
}
}
document.getElementById('btnTranslate').addEventListener('click', function() {
const text = document.getElementById('categoryName').value;
if (!text) {
alert('Please enter a category name first.');
async function translateTo(targetLang) {
const sourceId = targetLang === 'Arabic' ? 'categoryName' : 'categoryNameAr';
const targetId = targetLang === 'Arabic' ? 'categoryNameAr' : 'categoryName';
const btnId = targetLang === 'Arabic' ? 'translateBtnAr' : 'translateBtnEn';
const sourceText = document.getElementById(sourceId).value;
if (!sourceText) {
alert('Please enter text to translate first.');
return;
}
const btn = this;
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm text-primary" role="status" aria-hidden="true"></span>';
const btn = document.getElementById(btnId);
const originalContent = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Translating...';
btn.classList.add('disabled');
fetch('../api/translate.php', {
try {
const response = await fetch('../api/translate.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: text,
target_lang: 'Arabic'
}),
})
.then(response => response.json())
.then(data => {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: sourceText, target_lang: targetLang })
});
const data = await response.json();
if (data.success) {
document.getElementById('categoryNameAr').value = data.translated_text;
document.getElementById(targetId).value = data.translated_text;
} else {
alert('Translation failed: ' + (data.error || 'Unknown error'));
alert('Translation error: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
} catch (error) {
console.error('Translation error:', error);
alert('An error occurred during translation.');
})
.finally(() => {
btn.disabled = false;
btn.innerHTML = originalHtml;
});
});
} finally {
btn.innerHTML = originalContent;
btn.classList.remove('disabled');
}
}
</script>
<?php endif; ?>

View File

@ -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) {
}
?>
<!doctype html>
<html lang="en">
<html lang="<?= $currentLang ?>" dir="<?= $isRTL ? 'rtl' : 'ltr' ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -69,13 +92,13 @@ function can_view($module) {
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../assets/css/custom.css?v=<?= time() ?>">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
/* Base styles using CSS variables for theming */
body {
font-family: 'Inter', sans-serif;
font-family: 'Inter', 'Noto Sans Arabic', sans-serif;
background-color: var(--bg-body);
color: var(--text-primary);
}
@ -83,12 +106,11 @@ function can_view($module) {
height: 100vh;
position: fixed;
top: 0;
left: 0;
<?= $isRTL ? 'right: 0; border-left: 1px solid var(--border-color); border-right: none;' : 'left: 0; border-right: 1px solid var(--border-color); border-left: none;' ?>
width: 250px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
padding-top: 0;
z-index: 1000;
z-index: 1060;
overflow-y: auto;
transition: background-color 0.3s ease, border-color 0.3s ease;
}
@ -107,11 +129,12 @@ function can_view($module) {
font-weight: 500;
font-size: 0.95rem;
transition: all 0.2s ease;
<?= $isRTL ? 'text-align: right;' : '' ?>
}
.sidebar .nav-link:hover, .sidebar .nav-link.active {
color: var(--sidebar-active-color);
background: var(--sidebar-active-bg);
border-right: 3px solid var(--sidebar-active-border);
<?= $isRTL ? 'border-left: 3px solid var(--sidebar-active-border); border-right: none;' : 'border-right: 3px solid var(--sidebar-active-border); border-left: none;' ?>
}
.sidebar-heading {
font-size: 0.75rem;
@ -126,21 +149,21 @@ function can_view($module) {
width: 100%;
background: none;
border: none;
text-align: left;
<?= $isRTL ? 'text-align: right; flex-direction: row-reverse;' : 'text-align: left;' ?>
}
.sidebar-heading:hover {
color: var(--accent-color);
}
.sidebar-heading i {
.sidebar-heading i:first-child {
font-size: 1rem;
margin-right: 0.5rem;
<?= $isRTL ? 'margin-left: 0.5rem; margin-right: 0;' : 'margin-right: 0.5rem; margin-left: 0;' ?>
color: var(--accent-color);
}
.main-content { margin-left: 250px; padding: 2rem; }
.main-content { <?= $isRTL ? 'margin-right: 250px; margin-left: 0;' : 'margin-left: 250px; margin-right: 0;' ?> padding: 2rem; }
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); transition: transform 0.3s ease; top: 56px; height: calc(100vh - 56px); }
.sidebar { transform: <?= $isRTL ? 'translateX(100%)' : 'translateX(-100%)' ?>; transition: transform 0.3s ease; top: 0; height: 100vh; }
.sidebar.show { transform: translateX(0); }
.main-content { margin-left: 0; }
.main-content { margin-left: 0; margin-right: 0; }
.sidebar-header { display: none !important; } /* Hide duplicate logo on mobile */
}
.stat-card {
@ -167,9 +190,14 @@ function can_view($module) {
.dropdown-menu {
background-color: var(--bg-card);
border-color: var(--border-color);
<?= $isRTL ? 'text-align: right;' : '' ?>
}
.dropdown-item {
color: var(--text-primary);
<?= $isRTL ? 'text-align: right;' : '' ?>
}
.dropdown-item i {
<?= $isRTL ? 'margin-left: 0.5rem; margin-right: 0;' : 'margin-right: 0.5rem; margin-left: 0;' ?>
}
.dropdown-item:hover {
background-color: var(--bg-body);
@ -177,6 +205,13 @@ function can_view($module) {
}
.dropdown-header {
color: var(--text-secondary);
<?= $isRTL ? 'text-align: right;' : '' ?>
}
.offcanvas-start {
<?= $isRTL ? 'right: 0; left: auto; transform: translateX(100%);' : '' ?>
}
.offcanvas-start.show {
<?= $isRTL ? 'transform: translateX(0);' : '' ?>
}
</style>
<script>
@ -219,7 +254,7 @@ function can_view($module) {
</nav>
<!-- Sidebar -->
<div class="offcanvas-md offcanvas-start sidebar" tabindex="-1" id="sidebarMenu">
<div class="offcanvas-md offcanvas-<?= $isRTL ? 'end' : 'start' ?> sidebar" tabindex="-1" id="sidebarMenu">
<div class="d-flex align-items-center justify-content-center sidebar-header d-none d-md-flex">
<a href="index.php" class="text-decoration-none">
<?php if ($logoUrl): ?>
@ -229,8 +264,8 @@ function can_view($module) {
<?php endif; ?>
</a>
</div>
<div class="px-4 py-3 d-md-none">
<h5 class="fw-bold" style="color: var(--text-primary);">Menu</h5>
<div class="px-4 py-3 d-md-none d-flex justify-content-between align-items-center">
<h5 class="fw-bold m-0" style="color: var(--text-primary);"><?= t('menu_management') ?></h5><button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#sidebarMenu" aria-label="Close"></button>
</div>
<div class="sidebar-content accordion" id="sidebarAccordion">
@ -238,7 +273,7 @@ function can_view($module) {
<?php if (can_view('dashboard')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('index.php') ?>" href="index.php">
<i class="bi bi-grid me-2"></i> Dashboard
<i class="bi bi-grid me-2"></i> <?= t('dashboard') ?>
</a>
</li>
<?php endif; ?>
@ -252,7 +287,7 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($posGroup) ?>"
data-bs-toggle="collapse" href="#collapsePos" role="button" aria-expanded="<?= isGroupExpanded($posGroup) ?>" aria-controls="collapsePos">
<span><i class="bi bi-shop"></i> POS & Operations</span>
<span><i class="bi bi-shop"></i> <?= t('pos_operations') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($posGroup) ?>" id="collapsePos" data-bs-parent="#sidebarAccordion">
@ -260,28 +295,28 @@ function can_view($module) {
<?php if (can_view('pos')): ?>
<li class="nav-item">
<a class="nav-link" href="../pos.php" target="_blank">
<i class="bi bi-display me-2"></i> POS Terminal
<i class="bi bi-display me-2"></i> <?= t('pos_terminal') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('orders')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('orders.php') ?>" href="orders.php">
<i class="bi bi-receipt me-2"></i> Orders (POS)
<i class="bi bi-receipt me-2"></i> <?= t('orders_pos') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('kitchen')): ?>
<li class="nav-item">
<a class="nav-link" href="../kitchen.php" target="_blank">
<i class="bi bi-fire me-2"></i> Kitchen View
<i class="bi bi-fire me-2"></i> <?= t('kitchen_view') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('ads')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('ads.php') || isActive('ad_edit.php') ? 'active' : '' ?>" href="ads.php">
<i class="bi bi-megaphone me-2"></i> Ads Management
<i class="bi bi-megaphone me-2"></i> <?= t('ads_management') ?>
</a>
</li>
<?php endif; ?>
@ -298,7 +333,7 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($menuGroup) ?>"
data-bs-toggle="collapse" href="#collapseMenu" role="button" aria-expanded="<?= isGroupExpanded($menuGroup) ?>" aria-controls="collapseMenu">
<span><i class="bi bi-menu-button-wide"></i> Menu Management</span>
<span><i class="bi bi-menu-button-wide"></i> <?= t('menu_management') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($menuGroup) ?>" id="collapseMenu" data-bs-parent="#sidebarAccordion">
@ -306,14 +341,14 @@ function can_view($module) {
<?php if (can_view('products')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('products.php') ?>" href="products.php">
<i class="bi bi-box-seam me-2"></i> Products
<i class="bi bi-box-seam me-2"></i> <?= t('products') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('categories')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('categories.php') ?>" href="categories.php">
<i class="bi bi-tags me-2"></i> Categories
<i class="bi bi-tags me-2"></i> <?= t('categories') ?>
</a>
</li>
<?php endif; ?>
@ -330,7 +365,7 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($setupGroup) ?>"
data-bs-toggle="collapse" href="#collapseSetup" role="button" aria-expanded="<?= isGroupExpanded($setupGroup) ?>" aria-controls="collapseSetup">
<span><i class="bi bi-gear-wide-connected"></i> Restaurant Setup</span>
<span><i class="bi bi-gear-wide-connected"></i> <?= t('restaurant_setup') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($setupGroup) ?>" id="collapseSetup" data-bs-parent="#sidebarAccordion">
@ -338,21 +373,21 @@ function can_view($module) {
<?php if (can_view('outlets')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('outlets.php') ?>" href="outlets.php">
<i class="bi bi-shop me-2"></i> Outlets
<i class="bi bi-shop me-2"></i> <?= t('outlets') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('areas')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('areas.php') ?>" href="areas.php">
<i class="bi bi-geo-alt me-2"></i> Areas
<i class="bi bi-geo-alt me-2"></i> <?= t('areas') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('tables')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('tables.php') ?>" href="tables.php">
<i class="bi bi-ui-checks-grid me-2"></i> Tables
<i class="bi bi-ui-checks-grid me-2"></i> <?= t('tables') ?>
</a>
</li>
<?php endif; ?>
@ -369,7 +404,7 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($peopleGroup) ?>"
data-bs-toggle="collapse" href="#collapsePeople" role="button" aria-expanded="<?= isGroupExpanded($peopleGroup) ?>" aria-controls="collapsePeople">
<span><i class="bi bi-people"></i> People & Partners</span>
<span><i class="bi bi-people"></i> <?= t('people_partners') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($peopleGroup) ?>" id="collapsePeople" data-bs-parent="#sidebarAccordion">
@ -377,21 +412,21 @@ function can_view($module) {
<?php if (can_view('customers')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('customers.php') ?>" href="customers.php">
<i class="bi bi-people-fill me-2"></i> Customers
<i class="bi bi-people-fill me-2"></i> <?= t('customers') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('suppliers')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('suppliers.php') ?>" href="suppliers.php">
<i class="bi bi-truck me-2"></i> Suppliers
<i class="bi bi-truck me-2"></i> <?= t('suppliers') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('loyalty')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('loyalty.php') ?>" href="loyalty.php">
<i class="bi bi-award me-2"></i> Loyalty
<i class="bi bi-award me-2"></i> <?= t('loyalty') ?>
</a>
</li>
<?php endif; ?>
@ -408,7 +443,7 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($financialsGroup) ?>"
data-bs-toggle="collapse" href="#collapseFinancials" role="button" aria-expanded="<?= isGroupExpanded($financialsGroup) ?>" aria-controls="collapseFinancials">
<span><i class="bi bi-wallet2"></i> Financials</span>
<span><i class="bi bi-wallet2"></i> <?= t('financials') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($financialsGroup) ?>" id="collapseFinancials" data-bs-parent="#sidebarAccordion">
@ -416,21 +451,21 @@ function can_view($module) {
<?php if (can_view('purchases')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('purchases.php') ? 'active' : '' ?>" href="purchases.php">
<i class="bi bi-cart-plus me-2"></i> Purchases
<i class="bi bi-cart-plus me-2"></i> <?= t('purchases') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('expenses')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('expenses.php') ? 'active' : '' ?>" href="expenses.php">
<i class="bi bi-cash-stack me-2"></i> Expenses
<i class="bi bi-cash-stack me-2"></i> <?= t('expenses') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('expense_categories')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('expense_categories.php') || isActive('expense_category_edit.php') ? 'active' : '' ?>" href="expense_categories.php">
<i class="bi bi-tags me-2"></i> Expense Categories
<i class="bi bi-tags me-2"></i> <?= t('expense_categories') ?>
</a>
</li>
<?php endif; ?>
@ -446,14 +481,14 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($reportsGroup) ?>"
data-bs-toggle="collapse" href="#collapseReports" role="button" aria-expanded="<?= isGroupExpanded($reportsGroup) ?>" aria-controls="collapseReports">
<span><i class="bi bi-bar-chart-line"></i> Reports & Analytics</span>
<span><i class="bi bi-bar-chart-line"></i> <?= t('reports_analytics') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($reportsGroup) ?>" id="collapseReports" data-bs-parent="#sidebarAccordion">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link <?= isActive('reports.php') ?>" href="reports.php">
<i class="bi bi-graph-up me-2"></i> Daily Reports
<i class="bi bi-graph-up me-2"></i> <?= t('daily_reports') ?>
</a>
</li>
</ul>
@ -469,7 +504,7 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($userGroupPages) ?>"
data-bs-toggle="collapse" href="#collapseUsers" role="button" aria-expanded="<?= isGroupExpanded($userGroupPages) ?>" aria-controls="collapseUsers">
<span><i class="bi bi-person-badge"></i> User Management</span>
<span><i class="bi bi-person-badge"></i> <?= t('user_management') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($userGroupPages) ?>" id="collapseUsers" data-bs-parent="#sidebarAccordion">
@ -477,25 +512,25 @@ function can_view($module) {
<?php if (can_view('users')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('users.php') ?>" href="users.php">
<i class="bi bi-people me-2"></i> Users
<i class="bi bi-people me-2"></i> <?= t('users') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('user_groups')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('user_groups.php') ?>" href="user_groups.php">
<i class="bi bi-shield-lock me-2"></i> Roles / Groups
<i class="bi bi-shield-lock me-2"></i> <?= t('roles_groups') ?>
</a>
</li>
<?php endif; ?>
<li class="nav-item">
<a class="nav-link <?= isActive('attendance.php') ?>" href="attendance.php">
<i class="bi bi-calendar-check me-2"></i> Attendance
<i class="bi bi-calendar-check me-2"></i> <?= t('attendance') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= isActive('ratings.php') ?>" href="ratings.php">
<i class="bi bi-star me-2"></i> Staff Ratings
<i class="bi bi-star me-2"></i> <?= t('staff_ratings') ?>
</a>
</li>
</ul>
@ -511,7 +546,7 @@ function can_view($module) {
<div class="nav-group">
<a class="sidebar-heading d-flex justify-content-between align-items-center text-decoration-none <?= getGroupToggleClass($settingsGroup) ?>"
data-bs-toggle="collapse" href="#collapseSettings" role="button" aria-expanded="<?= isGroupExpanded($settingsGroup) ?>" aria-controls="collapseSettings">
<span><i class="bi bi-sliders"></i> Settings</span>
<span><i class="bi bi-sliders"></i> <?= t('settings') ?></span>
<i class="bi bi-chevron-down chevron-icon"></i>
</a>
<div class="collapse <?= isGroupActive($settingsGroup) ?>" id="collapseSettings" data-bs-parent="#sidebarAccordion">
@ -519,31 +554,31 @@ function can_view($module) {
<?php if (can_view('payment_types')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('payment_types.php') ?>" href="payment_types.php">
<i class="bi bi-credit-card me-2"></i> Payment Types
<i class="bi bi-credit-card me-2"></i> <?= t('payment_types') ?>
</a>
</li>
<?php endif; ?>
<?php if (can_view('settings')): ?>
<li class="nav-item">
<a class="nav-link <?= isActive('integrations.php') ?>" href="integrations.php">
<i class="bi bi-plugin me-2"></i> Integrations
<i class="bi bi-plugin me-2"></i> <?= t('integrations') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= isActive('company.php') ?>" href="company.php">
<i class="bi bi-building me-2"></i> Company
<i class="bi bi-building me-2"></i> <?= t('company') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= isActive('backup.php') ?>" href="backup.php">
<i class="bi bi-cloud-arrow-down me-2"></i> Backup & Restore
<i class="bi bi-cloud-arrow-down me-2"></i> <?= t('backup_restore') ?>
</a>
</li>
<?php endif; ?>
<li class="nav-item border-top mt-2 pt-2">
<a class="nav-link text-muted" href="../index.php" target="_blank">
<i class="bi bi-box-arrow-up-right me-2"></i> View Site
<i class="bi bi-box-arrow-up-right me-2"></i> <?= t('view_site') ?>
</a>
</li>
</ul>
@ -557,19 +592,32 @@ function can_view($module) {
<!-- Top Header -->
<header class="d-flex justify-content-end align-items-center mb-4 pb-3 border-bottom">
<div class="d-flex align-items-center gap-3">
<!-- Language Switcher -->
<div class="dropdown">
<button class="btn btn-link text-decoration-none dropdown-toggle d-flex align-items-center gap-2" type="button" id="topLangDropdown" data-bs-toggle="dropdown" aria-expanded="false" style="color: var(--text-primary);">
<i class="bi bi-translate"></i>
<span class="d-none d-sm-inline"><?= t('language') ?></span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow border-0" aria-labelledby="topLangDropdown">
<li><h6 class="dropdown-header"><?= t('select_language') ?></h6></li>
<li><a class="dropdown-item d-flex align-items-center gap-2" href="?lang=en"><?= t('english') ?></a></li>
<li><a class="dropdown-item d-flex align-items-center gap-2" href="?lang=ar"><?= t('arabic') ?></a></li>
</ul>
</div>
<!-- Theme Switcher -->
<div class="dropdown">
<button class="btn btn-link text-decoration-none dropdown-toggle d-flex align-items-center gap-2" type="button" id="topThemeDropdown" data-bs-toggle="dropdown" aria-expanded="false" style="color: var(--text-primary);">
<i class="bi bi-palette"></i>
<span class="d-none d-sm-inline">Theme</span>
<span class="d-none d-sm-inline"><?= t('theme') ?></span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow border-0" aria-labelledby="topThemeDropdown">
<li><h6 class="dropdown-header">Select Theme</h6></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('default')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#eee;border:1px solid #ddd"></span> Default</button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('dark')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#333"></span> Dark</button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('ocean')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#0077B6"></span> Ocean</button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('forest')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#2D6A4F"></span> Forest</button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('grape')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#7B1FA2"></span> Grape</button></li>
<li><h6 class="dropdown-header"><?= t('select_theme') ?></h6></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('default')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#eee;border:1px solid #ddd"></span> <?= t('default') ?></button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('dark')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#333"></span> <?= t('dark') ?></button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('ocean')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#0077B6"></span> <?= t('ocean') ?></button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('forest')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#2D6A4F"></span> <?= t('forest') ?></button></li>
<li><button class="dropdown-item d-flex align-items-center gap-2" onclick="setTheme('grape')"><span class="d-inline-block rounded-circle" style="width:12px;height:12px;background:#7B1FA2"></span> <?= t('grape') ?></button></li>
</ul>
</div>
@ -584,12 +632,12 @@ function can_view($module) {
<span class="d-none d-sm-inline fw-medium"><?= htmlspecialchars($userName) ?></span>
</a>
<ul class="dropdown-menu dropdown-menu-end shadow border-0" aria-labelledby="userDropdown">
<li><span class="dropdown-item-text text-muted small">Signed in as <?= htmlspecialchars($userGroup) ?></span></li>
<li><span class="dropdown-item-text text-muted small"><?= t('signed_in_as') ?> <?= htmlspecialchars($userGroup) ?></span></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="profile.php"><i class="bi bi-person me-2"></i> My Profile</a></li>
<li><a class="dropdown-item" href="company.php"><i class="bi bi-building me-2"></i> Company Settings</a></li>
<li><a class="dropdown-item" href="profile.php"><i class="bi bi-person me-2"></i> <?= t('my_profile') ?></a></li>
<li><a class="dropdown-item" href="company.php"><i class="bi bi-building me-2"></i> <?= t('company_settings') ?></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="<?= get_base_url() ?>logout.php"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li>
<li><a class="dropdown-item text-danger" href="<?= get_base_url() ?>logout.php"><i class="bi bi-box-arrow-right me-2"></i> <?= t('logout') ?></a></li>
</ul>
</div>
</div>

View File

@ -130,12 +130,12 @@ include 'includes/header.php';
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="fw-bold mb-1">Products Catalog</h2>
<h2 class="fw-bold mb-1"><?= t('products') ?></h2>
<p class="text-muted mb-0">Manage items, stock, and pricing</p>
</div>
<?php if (has_permission('products_add')): ?>
<button class="btn btn-primary btn-lg shadow-sm" data-bs-toggle="modal" data-bs-target="#productModal" onclick="prepareAddForm()" style="border-radius: 12px;">
<i class="bi bi-plus-lg me-1"></i> Add Product
<i class="bi bi-plus-lg me-1"></i> <?= t('add') ?> <?= t('products') ?>
</button>
<?php endif; ?>
</div>
@ -147,12 +147,12 @@ include 'includes/header.php';
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text bg-light border-0 text-muted"><i class="bi bi-search"></i></span>
<input type="text" name="search" class="form-control border-0 bg-light" placeholder="Search products..." value="<?= htmlspecialchars($search) ?>" style="border-radius: 0 10px 10px 0;">
<input type="text" name="search" class="form-control border-0 bg-light" placeholder="<?= t('search') ?>..." value="<?= htmlspecialchars($search) ?>" style="border-radius: 0 10px 10px 0;">
</div>
</div>
<div class="col-md-3">
<select name="category_filter" class="form-select border-0 bg-light rounded-3" onchange="this.form.submit()">
<option value="">All Categories</option>
<option value=""><?= t('all') ?> <?= t('categories') ?></option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= $category_filter == $cat['id'] ? 'selected' : '' ?>><?= htmlspecialchars($cat['name']) ?></option>
<?php endforeach; ?>
@ -164,7 +164,7 @@ include 'includes/header.php';
<?php if (empty($products)): ?>
<div class="text-center py-5 bg-white rounded-4 shadow-sm">
<i class="bi bi-box-seam display-1 text-muted opacity-25 mb-3 d-block"></i>
<h4 class="text-dark">No products found</h4>
<h4 class="text-dark"><?= t('none') ?></h4>
<p class="text-muted">Try adjusting your filters or search terms.</p>
</div>
<?php else: ?>
@ -173,12 +173,12 @@ include 'includes/header.php';
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">Product</th>
<th>Category</th>
<th>Stock</th>
<th>Price</th>
<th class="ps-4"><?= t('products') ?></th>
<th><?= t('category') ?></th>
<th><?= t('stock') ?></th>
<th><?= t('price') ?></th>
<th>Promotion</th>
<th class="text-end pe-4">Actions</th>
<th class="text-end pe-4"><?= t('actions') ?></th>
</tr>
</thead>
<tbody>
@ -205,11 +205,11 @@ include 'includes/header.php';
</div>
</div>
</td>
<td><span class="badge bg-light text-dark border px-2 py-1"><?= htmlspecialchars($product['category_name'] ?? 'Uncategorized') ?></span></td>
<td><span class="badge bg-light text-dark border px-2 py-1"><?= htmlspecialchars($product['category_name'] ?? t('none')) ?></span></td>
<td>
<?php $stock = (int)$product['stock_quantity']; ?>
<span class="badge <?= $stock <= 5 ? 'bg-danger-subtle text-danger' : ($stock <= 20 ? 'bg-warning-subtle text-warning' : 'bg-success-subtle text-success') ?> px-2 py-1 border border-<?= $stock <= 5 ? 'danger' : ($stock <= 20 ? 'warning' : 'success') ?>">
<?= $stock ?> in stock
<?= $stock ?> <?= t('stock') ?>
</span>
</td>
<td>
@ -225,7 +225,7 @@ include 'includes/header.php';
<?php if ($is_promo_active): ?>
<span class="badge bg-success rounded-pill px-2">-<?= floatval($product['promo_discount_percent']) ?>%</span>
<?php elseif (!empty($product['promo_discount_percent'])): ?>
<span class="badge bg-secondary rounded-pill px-2">Inactive</span>
<span class="badge bg-secondary rounded-pill px-2"><?= t('inactive') ?></span>
<?php else: ?>
<span class="text-muted small">-</span>
<?php endif; ?>
@ -246,11 +246,11 @@ include 'includes/header.php';
<?php if (has_permission('products_edit') || has_permission('products_add')): ?>
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3"
data-bs-toggle="modal" data-bs-target="#productModal"
onclick='prepareEditForm(<?= htmlspecialchars(json_encode($product), ENT_QUOTES, "UTF-8") ?>)'>Edit</button>
onclick='prepareEditForm(<?= htmlspecialchars(json_encode($product), ENT_QUOTES, "UTF-8") ?>)'><?= t('edit') ?></button>
<?php endif; ?>
<?php if (has_permission('products_del')): ?>
<a href="?delete=<?= $product['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Delete this product? This action cannot be undone.')">Delete</a>
<a href="?delete=<?= $product['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Delete this product? This action cannot be undone.')"><?= t('delete') ?></a>
<?php endif; ?>
</div>
</td>
@ -271,7 +271,7 @@ include 'includes/header.php';
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header bg-primary text-white border-0 py-3">
<h5 class="modal-title fw-bold" id="productModalTitle">Add New Product</h5>
<h5 class="modal-title fw-bold" id="productModalTitle"><?= t('add') ?> <?= t('products') ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" id="productForm" enctype="multipart/form-data">
@ -282,13 +282,18 @@ include 'includes/header.php';
<div class="row g-3 mb-4">
<div class="col-md-8">
<div class="mb-3">
<label class="form-label small fw-bold text-muted">PRODUCT NAME (EN) <span class="text-danger">*</span></label>
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('name') ?> (EN) <span class="text-danger">*</span></span>
<a href="javascript:void(0)" onclick="translateTo('English')" class="text-decoration-none small text-primary fw-bold" id="translateBtnEn">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="name" id="productName" class="form-control rounded-3" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span>PRODUCT NAME (ARABIC)</span>
<a href="javascript:void(0)" onclick="translateName()" class="text-decoration-none small text-primary fw-bold" id="translateBtn">
<span><?= t('arabic_name') ?></span>
<a href="javascript:void(0)" onclick="translateTo('Arabic')" class="text-decoration-none small text-primary fw-bold" id="translateBtnAr">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
@ -298,7 +303,7 @@ include 'includes/header.php';
</div>
<div class="row g-3">
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold text-muted">CATEGORY <span class="text-danger">*</span></label>
<label class="form-label small fw-bold text-muted"><?= t('category') ?> <span class="text-danger">*</span></label>
<select name="category_id" id="productCategoryId" class="form-select rounded-3" required>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= htmlspecialchars($cat['name']) ?></option>
@ -306,7 +311,7 @@ include 'includes/header.php';
</select>
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold text-muted">SELLING PRICE <span class="text-danger">*</span></label>
<label class="form-label small fw-bold text-muted"><?= t('price') ?> <span class="text-danger">*</span></label>
<div class="input-group">
<span class="input-group-text bg-light border-0"><?= get_company_settings()['currency_symbol'] ?></span>
<input type="number" step="0.01" name="price" id="productPrice" class="form-control rounded-3 border-0 bg-light" required>
@ -319,7 +324,7 @@ include 'includes/header.php';
<input type="number" step="0.01" name="cost_price" id="productCostPrice" class="form-control rounded-3 border-0 bg-light">
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-bold text-muted">STOCK QUANTITY</label>
<label class="form-label small fw-bold text-muted"><?= t('stock') ?></label>
<input type="number" name="stock_quantity" id="productStockQuantity" class="form-control rounded-3 border-0 bg-light">
</div>
</div>
@ -346,7 +351,7 @@ include 'includes/header.php';
</div>
<div class="mb-4">
<label class="form-label small fw-bold text-muted">DESCRIPTION</label>
<label class="form-label small fw-bold text-muted"><?= t('description') ?></label>
<textarea name="description" id="productDescription" class="form-control rounded-3" rows="3" placeholder="Describe your product..."></textarea>
</div>
@ -362,8 +367,8 @@ include 'includes/header.php';
</div>
</div>
<div class="modal-footer border-0 p-4 pt-0">
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold shadow-sm">Save Product</button>
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal"><?= t('cancel') ?></button>
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold shadow-sm"><?= t('save') ?></button>
</div>
</form>
</div>
@ -372,7 +377,7 @@ include 'includes/header.php';
<script>
function prepareAddForm() {
document.getElementById('productModalTitle').innerText = 'Add New Product';
document.getElementById('productModalTitle').innerText = '<?= t('add') ?> <?= t('products') ?>';
document.getElementById('productAction').value = 'add_product';
document.getElementById('productForm').reset();
document.getElementById('productId').value = '';
@ -381,7 +386,7 @@ function prepareAddForm() {
function prepareEditForm(prod) {
if (!prod) return;
document.getElementById('productModalTitle').innerText = 'Edit Product: ' + prod.name;
document.getElementById('productModalTitle').innerText = '<?= t('edit') ?> <?= t('products') ?>: ' + prod.name;
document.getElementById('productAction').value = 'edit_product';
document.getElementById('productId').value = prod.id;
document.getElementById('productName').value = prod.name;
@ -404,14 +409,18 @@ function prepareEditForm(prod) {
}
}
async function translateName() {
const enName = document.getElementById('productName').value;
if (!enName) {
alert('Please enter an English name first.');
async function translateTo(targetLang) {
const sourceId = targetLang === 'Arabic' ? 'productName' : 'productNameAr';
const targetId = targetLang === 'Arabic' ? 'productNameAr' : 'productName';
const btnId = targetLang === 'Arabic' ? 'translateBtnAr' : 'translateBtnEn';
const sourceText = document.getElementById(sourceId).value;
if (!sourceText) {
alert('Please enter text to translate first.');
return;
}
const btn = document.getElementById('translateBtn');
const btn = document.getElementById(btnId);
const originalContent = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Translating...';
btn.classList.add('disabled');
@ -420,11 +429,11 @@ async function translateName() {
const response = await fetch('../api/translate.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: enName, target_lang: 'Arabic' })
body: JSON.stringify({ text: sourceText, target_lang: targetLang })
});
const data = await response.json();
if (data.success) {
document.getElementById('productNameAr').value = data.translated_text;
document.getElementById(targetId).value = data.translated_text;
} else {
alert('Translation error: ' + data.error);
}

View File

@ -94,7 +94,7 @@ include 'includes/header.php';
?>
<div class="mb-4">
<h2 class="fw-bold">My Profile</h2>
<h2 class="fw-bold"><?= t('my_profile') ?></h2>
<p class="text-muted">Manage your personal information and account settings.</p>
</div>
@ -107,17 +107,22 @@ include 'includes/header.php';
<form method="POST" enctype="multipart/form-data">
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label small fw-bold text-muted">FULL NAME (ENGLISH)</label>
<input type="text" name="full_name" id="full_name" class="form-control" value="<?= htmlspecialchars($user['full_name'] ?? '') ?>" required>
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('name') ?> (EN) <span class="text-danger">*</span></span>
<a href="javascript:void(0)" onclick="translateTo('English')" class="text-decoration-none small text-primary fw-bold" id="translateBtnEn">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="full_name" id="full_name" class="form-control rounded-3" value="<?= htmlspecialchars($user['full_name'] ?? '') ?>" required>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold text-muted">FULL NAME (ARABIC)</label>
<div class="input-group">
<input type="text" name="full_name_ar" id="full_name_ar" class="form-control" value="<?= htmlspecialchars($user['full_name_ar'] ?? '') ?>" dir="rtl">
<button type="button" class="btn btn-outline-secondary" onclick="translateName(event)">
<i class="bi bi-translate text-primary"></i>
</button>
</div>
<label class="form-label small fw-bold text-muted d-flex justify-content-between">
<span><?= t('arabic_name') ?></span>
<a href="javascript:void(0)" onclick="translateTo('Arabic')" class="text-decoration-none small text-primary fw-bold" id="translateBtnAr">
<i class="bi bi-translate me-1"></i> Auto-translate
</a>
</label>
<input type="text" name="full_name_ar" id="full_name_ar" class="form-control rounded-3 text-end" value="<?= htmlspecialchars($user['full_name_ar'] ?? '') ?>" dir="rtl">
</div>
</div>
@ -127,7 +132,7 @@ include 'includes/header.php';
<input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['username'] ?? '') ?>" readonly>
</div>
<div class="col-md-6">
<label class="form-label small fw-bold text-muted">EMAIL</label>
<label class="form-label small fw-bold text-muted"><?= t('email') ?></label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email'] ?? '') ?>" required>
</div>
</div>
@ -170,7 +175,7 @@ include 'includes/header.php';
<hr class="my-4">
<div class="d-flex justify-content-end gap-2">
<button type="submit" class="btn btn-primary rounded-pill px-5 fw-bold">Save Changes</button>
<button type="submit" class="btn btn-primary rounded-pill px-5 fw-bold"><?= t('save') ?></button>
</div>
</form>
</div>
@ -216,33 +221,37 @@ include 'includes/header.php';
</div>
<script>
function translateName(event) {
const fullName = document.getElementById('full_name').value;
if (!fullName) return;
async function translateTo(targetLang) {
const sourceId = targetLang === 'Arabic' ? 'full_name' : 'full_name_ar';
const targetId = targetLang === 'Arabic' ? 'full_name_ar' : 'full_name';
const btnId = targetLang === 'Arabic' ? 'translateBtnAr' : 'translateBtnEn';
const btn = event.currentTarget;
const icon = btn.querySelector('i');
icon.className = 'spinner-border spinner-border-sm text-primary';
btn.disabled = true;
const sourceText = document.getElementById(sourceId).value;
if (!sourceText) {
alert('Please enter text to translate first.');
return;
}
fetch('../api/translate.php', {
const btn = document.getElementById(btnId);
// Note: since the buttons are different elements here, we might need a better way to find them or just use alert
// Actually I'll just use the target lang to identify
try {
const response = await fetch('../api/translate.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: fullName,
target_lang: 'Arabic'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('full_name_ar').value = data.translated_text;
}
})
.finally(() => {
icon.className = 'bi bi-translate text-primary';
btn.disabled = false;
body: JSON.stringify({ text: sourceText, target_lang: targetLang })
});
const data = await response.json();
if (data.success) {
document.getElementById(targetId).value = data.translated_text;
} else {
alert('Translation error: ' + data.error);
}
} catch (error) {
console.error('Translation error:', error);
alert('An error occurred during translation.');
}
}
</script>

View File

@ -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).

View File

@ -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 = '<div class="p-4 text-center text-muted">No unpaid bills found.</div>';
recallList.innerHTML = `<div class="p-4 text-center text-muted">${_t('none')}</div>`;
}
})
.catch(() => {
recallList.innerHTML = '<div class="alert alert-danger">Error fetching orders.</div>';
recallList.innerHTML = `<div class="alert alert-danger">${_t('error')}</div>`;
});
}
@ -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 = '<div class="list-group-item text-muted">No results found</div>';
customerResults.innerHTML = `<div class="list-group-item text-muted">${_t('none')}</div>`;
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 = `<span>${v.name}</span><span class="fw-bold">${formatCurrency(finalPrice)}</span>`;
const vName = (LANG === 'ar' && v.name_ar) ? v.name_ar : v.name;
btn.innerHTML = `<span>${vName}</span><span class="fw-bold">${formatCurrency(finalPrice)}</span>`;
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 = '<div class="text-center text-muted mt-5"><i class="bi bi-basket3 fs-1 text-light"></i><p class="mt-2">Cart is empty</p></div>';
cartItemsContainer.innerHTML = `<div class="text-center text-muted mt-5"><i class="bi bi-basket3 fs-1 text-light"></i><p class="mt-2">${_t('cart_empty')}</p></div>`;
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 = `
<div class="flex-grow-1 me-2">
<div class="fw-bold text-truncate" style="max-width: 140px;">${item.name}</div>
${item.name_ar ? `<div class="text-primary small" dir="rtl" style="font-size: 0.75rem;">${item.name_ar}</div>` : ''}
<div class="fw-bold text-truncate" style="max-width: 140px;">${itemName}</div>
${otherName ? `<div class="text-muted small" style="font-size: 0.75rem;">${otherName}</div>` : ''}
<div class="small text-muted">${formatCurrency(item.price)}</div>
</div>
<div class="d-flex align-items-center bg-light rounded px-1">
@ -397,7 +405,7 @@ document.addEventListener('DOMContentLoaded', () => {
</div>
<div class="text-end ms-3" style="min-width: 60px;">
<div class="fw-bold">${formatCurrency(itemTotal)}</div>
<button class="btn btn-sm text-danger p-0 mt-1" style="font-size: 0.8rem;" onclick="removeFromCart(${index})">Remove</button>
<button class="btn btn-sm text-danger p-0 mt-1" style="font-size: 0.8rem;" onclick="removeFromCart(${index})">${_t('remove')}</button>
</div>
`;
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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

202
includes/lang.php Normal file
View File

@ -0,0 +1,202 @@
<?php
// Simple translation helper
function t($key, $lang = null) {
if (!$lang) {
$lang = $_SESSION['lang'] ?? 'en';
}
$translations = [
'en' => [
'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;
}

151
pos.php
View File

@ -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) {
}
?>
<!doctype html>
<html lang="en">
<html lang="<?= $currentLang ?>" dir="<?= $isRTL ? 'rtl' : 'ltr' ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
@ -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-<?= $isRTL ? 'left' : 'right' ?>: 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-<?= $isRTL ? 'right' : 'left' ?>: 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: <?= $isRTL ? 'right' : '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: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 { <?= $isRTL ? 'text-align: right;' : '' ?> }
</style>
</head>
<body>
@ -150,21 +171,32 @@ if (!$loyalty_settings) {
</a>
<div class="ms-auto d-flex align-items-center gap-2">
<!-- Language Switcher -->
<div class="dropdown me-1">
<button class="btn btn-outline-secondary btn-sm rounded-pill px-3 border-0 bg-light" type="button" data-bs-toggle="dropdown">
<i class="bi bi-translate me-1"></i> <span class="d-none d-sm-inline"><?= $isRTL ? 'العربية' : 'English' ?></span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2">
<li><a class="dropdown-item" href="?lang=en">English</a></li>
<li><a class="dropdown-item" href="?lang=ar">العربية</a></li>
</ul>
</div>
<?php if (count($outlets) > 1): ?>
<div class="dropdown me-1 d-none d-md-block">
<button class="btn btn-outline-secondary btn-sm rounded-pill px-3 border-0 bg-light" type="button" data-bs-toggle="dropdown">
<i class="bi bi-shop me-1"></i> Switch
<i class="bi bi-shop me-1"></i> <?= t('switch') ?>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2">
<?php foreach ($outlets as $o): ?>
<li><a class="dropdown-item <?= $o['id'] == $outlet_id ? 'active' : '' ?>" href="?outlet_id=<?= $o['id'] ?>&order_type=<?= $order_type ?>"><?= htmlspecialchars($o['name']) ?></a></li>
<li><a class="dropdown-item <?= $o['id'] == $outlet_id ? 'active' : '' ?>" href="?outlet_id=<?= $o['id'] ?>&order_type=<?= $order_type ?>"><?= htmlspecialchars(($isRTL && !empty($o['name_ar'])) ? $o['name_ar'] : $o['name']) ?></a></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<a href="kitchen.php" class="btn btn-outline-primary btn-sm rounded-pill px-2 px-sm-3 border-0"><i class="bi bi-fire me-1"></i> Kitchen</a>
<a href="admin/orders.php" class="btn btn-outline-secondary btn-sm rounded-pill px-2 px-sm-3 border-0"><i class="bi bi-receipt me-1"></i> Orders</a>
<button class="btn btn-outline-warning btn-sm rounded-pill px-2 px-sm-3 border-0" onclick="openRecallOrderModal()"><i class="bi bi-arrow-counterclockwise me-1"></i> Recall</button>
<a href="kitchen.php" class="btn btn-outline-primary btn-sm rounded-pill px-2 px-sm-3 border-0"><i class="bi bi-fire me-1"></i> <?= t('kitchen_view') ?></a>
<a href="admin/orders.php" class="btn btn-outline-secondary btn-sm rounded-pill px-2 px-sm-3 border-0"><i class="bi bi-receipt me-1"></i> <?= t('orders_pos') ?></a>
<button class="btn btn-outline-warning btn-sm rounded-pill px-2 px-sm-3 border-0" onclick="openRecallOrderModal()"><i class="bi bi-arrow-counterclockwise me-1"></i> <?= t('recall') ?></button>
<div class="vr mx-1 h-25 d-none d-sm-block"></div>
@ -175,12 +207,12 @@ if (!$loyalty_settings) {
<ul class="dropdown-menu dropdown-menu-end shadow border-0 mt-2">
<li class="px-3 py-2 border-bottom">
<div class="fw-bold small"><?= htmlspecialchars($currentUser['username']) ?></div>
<div class="text-muted" style="font-size: 0.7rem;">Logged in as <?= htmlspecialchars($currentUser['group_name'] ?? 'User') ?></div>
<div class="text-muted" style="font-size: 0.7rem;"><?= t('signed_in_as') ?> <?= htmlspecialchars($currentUser['group_name'] ?? 'User') ?></div>
</li>
<?php if (has_permission('dashboard_view')): ?>
<li><a class="dropdown-item py-2" href="admin/index.php"><i class="bi bi-speedometer2 me-2"></i> Dashboard</a></li>
<li><a class="dropdown-item py-2" href="admin/index.php"><i class="bi bi-speedometer2 me-2"></i> <?= t('dashboard') ?></a></li>
<?php endif; ?>
<li><a class="dropdown-item py-2 text-danger" href="logout.php"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li>
<li><a class="dropdown-item py-2 text-danger" href="logout.php"><i class="bi bi-box-arrow-right me-2"></i> <?= t('logout') ?></a></li>
</ul>
</div>
</div>
@ -192,7 +224,7 @@ if (!$loyalty_settings) {
<div class="col-md-2 col-auto pos-categories scrollable-y p-2" style="max-width: 170px;">
<button class="category-btn active mb-2" data-category="all">
<i class="bi bi-grid fs-5 text-inherit"></i>
<span style="font-size: 0.85rem; font-weight: 700;">All Items</span>
<span style="font-size: 0.85rem; font-weight: 700;"><?= t('all') ?></span>
</button>
<?php foreach ($categories as $cat): ?>
<button class="category-btn mb-2" data-category="<?= $cat['id'] ?>">
@ -201,7 +233,7 @@ if (!$loyalty_settings) {
<?php else: ?>
<i class="bi bi-tag fs-5 text-inherit"></i>
<?php endif; ?>
<span class="text-truncate" style="font-size: 0.85rem; font-weight: 700;"><?= htmlspecialchars($cat['name']) ?></span>
<span class="text-truncate" style="font-size: 0.85rem; font-weight: 700;"><?= htmlspecialchars(($isRTL && !empty($cat['name_ar'])) ? $cat['name_ar'] : $cat['name']) ?></span>
</button>
<?php endforeach; ?>
</div>
@ -216,14 +248,14 @@ if (!$loyalty_settings) {
<span class="position-absolute top-50 start-0 translate-middle-y ms-3 text-muted">
<i class="bi bi-search small"></i>
</span>
<input type="text" id="product-search" class="form-control form-control-sm ps-5 border-0 bg-light rounded-3" placeholder="Search products by name or SKU...">
<input type="text" id="product-search" class="form-control form-control-sm ps-5 border-0 bg-light rounded-3" placeholder="<?= t('search') ?>...">
</div>
</div>
<?php if (count($outlets) > 1): ?>
<div class="col-auto">
<select class="form-select form-select-sm border-0 bg-light rounded-3 fw-bold text-primary" style="min-width: 120px;" onchange="location.href='?outlet_id=' + this.value + '&order_type=<?= $order_type ?>'">
<?php foreach ($outlets as $o): ?>
<option value="<?= $o['id'] ?>" <?= $o['id'] == $outlet_id ? 'selected' : '' ?>><?= htmlspecialchars($o['name']) ?></option>
<option value="<?= $o['id'] ?>" <?= $o['id'] == $outlet_id ? 'selected' : '' ?>><?= htmlspecialchars(($isRTL && !empty($o['name_ar'])) ? $o['name_ar'] : $o['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
@ -233,12 +265,13 @@ if (!$loyalty_settings) {
<!-- Grid Container -->
<div class="flex-grow-1 scrollable-y p-2">
<div class="row g-2 row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-8 row-cols-xxl-10" id="product-grid">
<div class="row g-2 row-cols-3 row-cols-sm-4 row-cols-md-5 row-cols-lg-6 row-cols-xl-7 row-cols-xxl-7" id="product-grid">
<?php foreach ($all_products as $product): ?>
<?php $has_variants = !empty($variants_by_product[$product['id']]); ?>
<div class="col product-item"
data-category="<?= $product['category_id'] ?>"
data-name="<?= htmlspecialchars(strtolower($product['name'])) ?>"
data-name-ar="<?= htmlspecialchars(strtolower($product['name_ar'] ?? '')) ?>"
data-sku="<?= htmlspecialchars(strtolower($product['sku'] ?? '')) ?>"
onclick="handleProductClick(<?= htmlspecialchars(json_encode($product), ENT_QUOTES) ?>, <?= htmlspecialchars(json_encode($variants_by_product[$product['id']] ?? []), ENT_QUOTES) ?>)">
<div class="card h-100 border-0 shadow-sm product-card rounded-3 overflow-hidden">
@ -258,8 +291,10 @@ if (!$loyalty_settings) {
</div>
<div class="card-body p-1 text-center d-flex flex-column justify-content-between">
<div>
<h6 class="card-title product-title mb-0"><?= htmlspecialchars($product['name']) ?></h6>
<?php if (!empty($product['name_ar'])): ?>
<h6 class="card-title product-title mb-0"><?= htmlspecialchars(($isRTL && !empty($product['name_ar'])) ? $product['name_ar'] : $product['name']) ?></h6>
<?php if ($isRTL && !empty($product['name'])): ?>
<div class="text-muted product-cat-name text-truncate" style="margin-top: -2px;"><?= htmlspecialchars($product['name']) ?></div>
<?php elseif (!$isRTL && !empty($product['name_ar'])): ?>
<div class="text-primary product-cat-name text-truncate" dir="rtl" style="margin-top: -2px;"><?= htmlspecialchars($product['name_ar']) ?></div>
<?php endif; ?>
</div>
@ -277,19 +312,19 @@ if (!$loyalty_settings) {
<div class="p-2 border-bottom bg-white">
<div class="btn-group w-100 mb-2" role="group">
<input type="radio" class="btn-check" name="order_type" id="ot-takeaway" value="takeaway" <?= $order_type === 'takeaway' ? 'checked' : '' ?>>
<label class="btn btn-outline-primary btn-sm py-1 rounded-start-pill" for="ot-takeaway" style="font-size: 0.75rem; font-weight: 700;">Takeaway</label>
<label class="btn btn-outline-primary btn-sm py-1 rounded-start-pill" for="ot-takeaway" style="font-size: 0.75rem; font-weight: 700;"><?= t('takeaway') ?></label>
<input type="radio" class="btn-check" name="order_type" id="ot-dine-in" value="dine-in" <?= $order_type === 'dine-in' ? 'checked' : '' ?>>
<label class="btn btn-outline-primary btn-sm py-1" for="ot-dine-in" style="font-size: 0.75rem; font-weight: 700;">Dine-In</label>
<label class="btn btn-outline-primary btn-sm py-1" for="ot-dine-in" style="font-size: 0.75rem; font-weight: 700;"><?= t('dine_in') ?></label>
<input type="radio" class="btn-check" name="order_type" id="ot-delivery" value="delivery" <?= $order_type === 'delivery' ? 'checked' : '' ?>>
<label class="btn btn-outline-primary btn-sm py-1 rounded-end-pill" for="ot-delivery" style="font-size: 0.75rem; font-weight: 700;">Delivery</label>
<label class="btn btn-outline-primary btn-sm py-1 rounded-end-pill" for="ot-delivery" style="font-size: 0.75rem; font-weight: 700;"><?= t('delivery') ?></label>
</div>
<div class="position-relative">
<div class="input-group input-group-sm">
<span class="input-group-text bg-light border-0 rounded-start-pill ps-3"><i class="bi bi-person text-muted"></i></span>
<input type="text" class="form-control border-0 bg-light ps-1" id="customer-search" placeholder="Customer Name/Phone" autocomplete="off" style="font-weight: 600;">
<span class="input-group-text bg-light border-0 rounded-<?= $isRTL ? 'end' : 'start' ?>-pill ps-3"><i class="bi bi-person text-muted"></i></span>
<input type="text" class="form-control border-0 bg-light ps-1" id="customer-search" placeholder="<?= t('customer') ?>..." autocomplete="off" style="font-weight: 600;">
<button class="btn btn-light border-0 d-none" type="button" id="clear-customer"><i class="bi bi-x"></i></button>
<button class="btn btn-light border-0 rounded-end-pill pe-3 text-primary" type="button" data-bs-toggle="modal" data-bs-target="#addCustomerModal"><i class="bi bi-plus-lg"></i></button>
<button class="btn btn-light border-0 rounded-<?= $isRTL ? 'start' : 'end' ?>-pill pe-3 text-primary" type="button" data-bs-toggle="modal" data-bs-target="#addCustomerModal"><i class="bi bi-plus-lg"></i></button>
</div>
<div class="list-group shadow border-0 search-dropdown" id="customer-results" style="border-radius: 12px; margin-top: 5px;"></div>
<input type="hidden" id="selected-customer-id">
@ -299,24 +334,24 @@ if (!$loyalty_settings) {
<div class="flex-grow-1 overflow-auto p-2 bg-white" id="cart-items">
<div class="text-center text-muted mt-5 opacity-50">
<i class="bi bi-basket3 fs-1 d-block mb-2"></i>
<p class="small">Cart is empty</p>
<p class="small"><?= t('none') ?></p>
</div>
</div>
<div class="p-3 border-top bg-light mt-auto rounded-top-4">
<div class="d-flex justify-content-between mb-1">
<span class="text-muted small">Subtotal</span>
<span class="text-muted small"><?= t('total') ?></span>
<span class="fw-bold small" id="cart-subtotal"><?= format_currency(0) ?></span>
</div>
<div class="d-flex justify-content-between mb-3">
<span class="fs-6 fw-bold">Total Payable</span>
<span class="fs-6 fw-bold"><?= t('total') ?></span>
<span class="fs-5 fw-bold text-primary" id="cart-total-price"><?= format_currency(0) ?></span>
</div>
<button class="btn btn-link text-danger w-100 py-0 fw-semibold mb-3 btn-sm text-decoration-none" onclick="clearCart()" style="font-size: 0.75rem;">Empty Cart</button>
<button class="btn btn-link text-danger w-100 py-0 fw-semibold mb-3 btn-sm text-decoration-none" onclick="clearCart()" style="font-size: 0.75rem;"><?= t('close') ?></button>
<div class="d-flex gap-2">
<button class="btn btn-primary w-50 btn-sm shadow-sm fw-bold py-2 rounded-3" id="quick-order-btn" disabled>QUICK PAY</button>
<button class="btn btn-outline-warning w-50 btn-sm shadow-sm fw-bold py-2 rounded-3" id="place-order-btn" disabled>SAVE BILL</button>
<button class="btn btn-primary w-50 btn-sm shadow-sm fw-bold py-2 rounded-3" id="quick-order-btn" disabled><?= t('save') ?></button>
<button class="btn btn-outline-warning w-50 btn-sm shadow-sm fw-bold py-2 rounded-3" id="place-order-btn" disabled><?= t('save') ?></button>
</div>
</div>
</div>
@ -329,7 +364,7 @@ if (!$loyalty_settings) {
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content border-0 shadow-lg" style="border-radius: 20px;">
<div class="modal-header border-0 pb-0">
<h6 class="modal-title fw-bold">Recall Unpaid Bill</h6>
<h6 class="modal-title fw-bold"><?= t('recall') ?></h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-3"><div id="recall-orders-list" class="list-group list-group-flush"></div></div>
@ -341,7 +376,7 @@ if (!$loyalty_settings) {
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content border-0 shadow-lg" style="border-radius: 20px;">
<div class="modal-header border-0 pb-0">
<h6 class="modal-title fw-bold" id="variantModalTitle">Options</h6>
<h6 class="modal-title fw-bold" id="variantModalTitle"><?= t('variant') ?></h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-3"><div id="variant-list" class="list-group list-group-flush"></div></div>
@ -353,19 +388,19 @@ if (!$loyalty_settings) {
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content border-0 shadow-lg" style="border-radius: 20px;">
<div class="modal-header border-0 pb-0">
<h6 class="modal-title fw-bold">New Customer</h6>
<h6 class="modal-title fw-bold"><?= t('add') ?> <?= t('customer') ?></h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-3">
<div class="mb-2">
<label class="small text-muted mb-1">Full Name</label>
<label class="small text-muted mb-1"><?= t('name') ?></label>
<input type="text" class="form-control form-control-sm rounded-3" id="new-customer-name">
</div>
<div class="mb-3">
<label class="small text-muted mb-1">Phone Number</label>
<label class="small text-muted mb-1"><?= t('phone') ?></label>
<input type="text" class="form-control form-control-sm rounded-3" id="new-customer-phone">
</div>
<button type="button" class="btn btn-primary btn-sm w-100 py-2 rounded-3" id="save-new-customer">Add Customer</button>
<button type="button" class="btn btn-primary btn-sm w-100 py-2 rounded-3" id="save-new-customer"><?= t('add') ?></button>
</div>
</div>
</div>
@ -375,7 +410,7 @@ if (!$loyalty_settings) {
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content border-0 shadow-lg" style="border-radius: 20px;">
<div class="modal-header border-0 pb-0">
<h6 class="modal-title fw-bold">Select Payment</h6>
<h6 class="modal-title fw-bold"><?= t('payment') ?></h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-3"><div class="row g-2" id="payment-methods-container"></div></div>
@ -393,6 +428,30 @@ if (!$loyalty_settings) {
const CURRENT_OUTLET = { id: <?= $outlet_id ?>, name: '<?= addslashes($current_outlet_name) ?>' };
const CURRENT_USER = { id: <?= $currentUser['id'] ?>, name: '<?= addslashes($currentUser['username']) ?>' };
const LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
const LANG = '<?= $currentLang ?>';
// JS Translations
const translations = {
'en': {
'cart_empty': 'Cart is empty',
'remove': 'Remove',
'order_placed': 'Order Placed',
'order_success': 'Order processed successfully!',
'error': 'Error',
'quick_pay': 'QUICK PAY',
'save_bill': 'SAVE BILL'
},
'ar': {
'cart_empty': 'السلة فارغة',
'remove': 'إزالة',
'order_placed': 'تم تقديم الطلب',
'order_success': 'تم معالجة الطلب بنجاح!',
'error': 'خطأ',
'quick_pay': 'دفع سريع',
'save_bill': 'حفظ الفاتورة'
}
};
const t = (key) => translations[LANG][key] || key;
</script>
<script src="assets/js/main.js?v=<?= time() ?>"></script>
</body>