Autosave: 20260419-032550
This commit is contained in:
parent
033b73cd60
commit
f6212d4e47
@ -74,18 +74,18 @@ require __DIR__ . '/includes/header.php';
|
||||
</section>
|
||||
|
||||
<section class="surface-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead>
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></th>
|
||||
<th><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></th>
|
||||
<th><?= h(tr('الوصف', 'Description')) ?></th>
|
||||
<th><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent">ID</th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الوصف', 'Description')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php if(empty($items)): ?>
|
||||
<tr><td colspan="5" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
@ -96,10 +96,10 @@ require __DIR__ . '/includes/header.php';
|
||||
<td><?= h($item['name_en']) ?></td>
|
||||
<td><?= h($item['description']) ?></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-light text-primary border" onclick="editItem(<?= htmlspecialchars(json_encode($item)) ?>)" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="editItem(<?= htmlspecialchars(json_encode($item)) ?>)" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light text-danger border" onclick="deleteItem(<?= $item['id'] ?>)" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="deleteItem(<?= $item['id'] ?>)" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@ -75,19 +75,19 @@ require __DIR__ . '/includes/header.php';
|
||||
</section>
|
||||
|
||||
<section class="surface-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead>
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= h(tr('الاسم', 'Name')) ?></th>
|
||||
<th><?= h(tr('الهاتف', 'Phone')) ?></th>
|
||||
<th><?= h(tr('البريد', 'Email')) ?></th>
|
||||
<th><?= h(tr('العنوان', 'Address')) ?></th>
|
||||
<th><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent">ID</th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الاسم', 'Name')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الهاتف', 'Phone')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('البريد', 'Email')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('العنوان', 'Address')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php if(empty($items)): ?>
|
||||
<tr><td colspan="6" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
@ -99,10 +99,10 @@ require __DIR__ . '/includes/header.php';
|
||||
<td><?= h($item['email']) ?></td>
|
||||
<td><?= h($item['address']) ?></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-light text-primary border" onclick="editItem(<?= htmlspecialchars(json_encode($item)) ?>)" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="editItem(<?= htmlspecialchars(json_encode($item)) ?>)" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light text-danger border" onclick="deleteItem(<?= $item['id'] ?>)" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="deleteItem(<?= $item['id'] ?>)" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
20
index.php
20
index.php
@ -95,18 +95,18 @@ require __DIR__ . '/includes/header.php';
|
||||
<a class="btn btn-primary" href="<?= h(url_for('pos.php')) ?>"><?= h(tr('إنشاء بيع POS', 'Create POS sale')) ?></a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th class="ps-3"><?= h(tr('رقم الإيصال', 'Receipt No')) ?></th>
|
||||
<th><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th><?= h(tr('النوع', 'Type')) ?></th>
|
||||
<th><?= h(tr('التاريخ', 'Date')) ?></th>
|
||||
<th class="text-end pe-3"><?= h(tr('الإجمالي', 'Total')) ?></th>
|
||||
<th class="ps-3 text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('رقم الإيصال', 'Receipt No')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('النوع', 'Type')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('التاريخ', 'Date')) ?></th>
|
||||
<th class="text-end pe-3 text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الإجمالي', 'Total')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php foreach ($metrics['recent'] as $sale): ?>
|
||||
<tr>
|
||||
<td class="ps-3">
|
||||
@ -122,7 +122,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
774
pos.php
774
pos.php
@ -1,3 +1,775 @@
|
||||
<?php
|
||||
$saleMode = 'pos';
|
||||
require_once __DIR__ . '/includes/sale_form.php';
|
||||
require_once __DIR__ . '/includes/app.php';
|
||||
$user = require_roles(['owner', 'manager', 'cashier']);
|
||||
$pageTitle = tr('نقاط البيع', 'Smart POS');
|
||||
$activeNav = 'pos';
|
||||
$error = '';
|
||||
$catalog = catalog();
|
||||
$allowedBranches = $user['role'] === 'owner' ? array_keys(branches()) : [$user['branch_code']];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$categories = $pdo->query('SELECT id, name_ar, name_en FROM categories ORDER BY name_ar ASC')->fetchAll();
|
||||
} catch (Throwable $e) {
|
||||
$categories = [];
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$branchCode = trim((string) ($_POST['branch_code'] ?? ''));
|
||||
$customerName = trim((string) ($_POST['customer_name'] ?? ''));
|
||||
$paymentMethod = trim((string) ($_POST['payment_method'] ?? 'cash'));
|
||||
$notes = trim((string) ($_POST['notes'] ?? ''));
|
||||
$cartJson = (string) ($_POST['cart_json'] ?? '[]');
|
||||
$items = json_decode($cartJson, true);
|
||||
|
||||
if (!in_array($branchCode, $allowedBranches, true)) {
|
||||
$error = tr('اختر فرعاً صالحاً لهذه الصلاحية.', 'Choose a valid branch for this role.');
|
||||
} elseif (!in_array($paymentMethod, ['cash', 'card', 'transfer'], true)) {
|
||||
$error = tr('اختر طريقة دفع صحيحة.', 'Choose a valid payment method.');
|
||||
} elseif (!is_array($items) || $items === []) {
|
||||
$error = tr('أضف صنفاً واحداً على الأقل إلى السلة.', 'Add at least one item to the cart.');
|
||||
} else {
|
||||
$normalized = [];
|
||||
$subtotal = 0.0;
|
||||
$itemCount = 0;
|
||||
foreach ($items as $item) {
|
||||
$sku = (string) ($item['sku'] ?? '');
|
||||
$qty = (int) ($item['qty'] ?? 0);
|
||||
if (!isset($catalog[$sku]) || $qty < 1) {
|
||||
continue;
|
||||
}
|
||||
$product = $catalog[$sku];
|
||||
$price = (float) $product['price'];
|
||||
$lineTotal = $price * $qty;
|
||||
$normalized[] = [
|
||||
'sku' => $sku,
|
||||
'name_ar' => $product['name_ar'],
|
||||
'name_en' => $product['name_en'],
|
||||
'qty' => $qty,
|
||||
'price' => $price,
|
||||
'line_total' => $lineTotal,
|
||||
];
|
||||
$subtotal += $lineTotal;
|
||||
$itemCount += $qty;
|
||||
}
|
||||
|
||||
if ($normalized === []) {
|
||||
$error = tr('السلة غير صالحة بعد التحقق من الأصناف.', 'The cart is invalid after product validation.');
|
||||
} else {
|
||||
$cashierName = current_lang() === 'ar' ? $user['name_ar'] : $user['name_en'];
|
||||
$saleId = create_sale([
|
||||
'receipt_no' => receipt_code(),
|
||||
'sale_mode' => $saleMode,
|
||||
'branch_code' => $branchCode,
|
||||
'cashier_username' => $user['username'],
|
||||
'cashier_name' => $cashierName,
|
||||
'role_name' => $user['role'],
|
||||
'customer_name' => $customerName !== '' ? $customerName : null,
|
||||
'payment_method' => $paymentMethod,
|
||||
'items' => $normalized,
|
||||
'item_count' => $itemCount,
|
||||
'subtotal' => $subtotal,
|
||||
'total_amount' => $subtotal,
|
||||
'notes' => $notes !== '' ? $notes : null,
|
||||
]);
|
||||
|
||||
set_flash('success', tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.'));
|
||||
redirect_to('sale.php', ['id' => $saleId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<style>
|
||||
/* Modern POS Styles */
|
||||
.pos-wrapper {
|
||||
height: calc(100vh - 140px); /* Adjust based on your header */
|
||||
min-height: 600px;
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.pos-products-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.pos-cart-area {
|
||||
width: 380px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Categories Scroll */
|
||||
.cat-scroll-container {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0.5rem;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.cat-scroll-container::-webkit-scrollbar {
|
||||
display: none; /* Chrome */
|
||||
}
|
||||
.cat-btn {
|
||||
white-space: nowrap;
|
||||
border-radius: 20px;
|
||||
padding: 0.5rem 1.25rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
color: #495057;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cat-btn:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.cat-btn.active {
|
||||
background: linear-gradient(90deg, #0d6efd, #0dcaf0);
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
box-shadow: 0 4px 10px rgba(13, 110, 253, 0.2);
|
||||
}
|
||||
|
||||
/* Products Grid */
|
||||
.products-grid {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 1.25rem;
|
||||
align-content: start;
|
||||
}
|
||||
.product-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid transparent;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
.product-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
||||
border-color: #0dcaf0;
|
||||
}
|
||||
.product-card:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.product-img-wrapper {
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
background: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.product-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.product-placeholder {
|
||||
font-size: 2rem;
|
||||
color: #dee2e6;
|
||||
}
|
||||
.product-info {
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
.product-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
color: #212529;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.2;
|
||||
height: 2.4em;
|
||||
}
|
||||
.product-price {
|
||||
font-weight: 700;
|
||||
color: #0d6efd;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
/* Cart Styles */
|
||||
.cart-header {
|
||||
background: linear-gradient(90deg, #0d6efd, #0dcaf0);
|
||||
color: #fff;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
.cart-items {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
.cart-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px dashed #e9ecef;
|
||||
}
|
||||
.cart-item-info {
|
||||
flex: 1;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.cart-item-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: #343a40;
|
||||
}
|
||||
.cart-item-price {
|
||||
color: #6c757d;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.cart-item-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 20px;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
.cart-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: #fff;
|
||||
color: #495057;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.cart-btn:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
.cart-qty {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.cart-footer {
|
||||
padding: 1.25rem;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
.summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
color: #495057;
|
||||
}
|
||||
.summary-total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: #212529;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
/* Pay / Action Buttons */
|
||||
.btn-pay {
|
||||
background: linear-gradient(90deg, #198754, #20c997);
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(25, 135, 84, 0.2);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-pay:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(25, 135, 84, 0.3);
|
||||
}
|
||||
.btn-pay:disabled {
|
||||
background: #adb5bd;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
[dir="ltr"] .cart-item-info {
|
||||
padding-right: 0;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php if ($error !== ''): ?>
|
||||
<div class="alert alert-danger shadow-sm border-0 rounded-3 mb-4"><?= h($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="pos-wrapper">
|
||||
<!-- Left Area: Products -->
|
||||
<div class="pos-products-area">
|
||||
<!-- Top Bar: Search & Hold -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
||||
<div class="position-relative" style="width: 300px;">
|
||||
<input type="text" id="posSearch" class="form-control rounded-pill ps-4" placeholder="<?= h(tr('بحث عن صنف...', 'Search item...')) ?>" autocomplete="off">
|
||||
<i class="bi bi-search position-absolute top-50 translate-middle-y text-muted" style="left: 15px;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-outline-primary rounded-pill px-4 shadow-sm" onclick="openHeldOrdersModal()">
|
||||
<i class="bi bi-clock-history me-1"></i> <span id="heldOrdersCount">0</span> <?= h(tr('قيد الانتظار', 'Held Orders')) ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Categories -->
|
||||
<div class="cat-scroll-container mb-4" id="catContainer">
|
||||
<button class="cat-btn active" data-cat="all" onclick="filterCat('all')"><?= h(tr('الكل', 'All')) ?></button>
|
||||
<?php foreach($categories as $cat):
|
||||
$catId = h($cat['id']);
|
||||
$catName = h(current_lang() === 'ar' ? $cat['name_ar'] : $cat['name_en']);
|
||||
?>
|
||||
<button class="cat-btn" data-cat="<?= $catId ?>" onclick="filterCat('<?= $catId ?>')"><?= $catName ?></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Grid -->
|
||||
<div class="products-grid" id="productsGrid">
|
||||
<?php foreach ($catalog as $sku => $item):
|
||||
$itemSku = h($sku);
|
||||
$itemName = h(current_lang() === 'ar' ? $item['name_ar'] : $item['name_en']);
|
||||
$itemPrice = h($item['price']);
|
||||
$itemCat = h($item['category_id'] ?? '');
|
||||
$imageUrl = !empty($item['image_url']) ? h($item['image_url']) : '';
|
||||
?>
|
||||
<div class="product-card" data-sku="<?= $itemSku ?>" data-name="<?= $itemName ?>" data-price="<?= $itemPrice ?>" data-cat="<?= $itemCat ?>" onclick="addToCart('<?= $itemSku ?>')">
|
||||
<div class="product-img-wrapper">
|
||||
<?php if (!empty($imageUrl)):
|
||||
$imgAlt = h(tr('صورة المنتج', 'Product Image'));
|
||||
?>
|
||||
<img src="<?= $imageUrl ?>" alt="<?= $imgAlt ?>" class="product-img" loading="lazy">
|
||||
<?php else:
|
||||
$placeholderText = h(tr('لا توجد صورة', 'No Image'));
|
||||
?>
|
||||
<i class="bi bi-image product-placeholder"></i>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<div class="product-title"><?= $itemName ?></div>
|
||||
<div class="product-price"><?= h(currency($itemPrice)) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Area: Smart Cart -->
|
||||
<div class="pos-cart-area">
|
||||
<div class="cart-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 fw-bold"><i class="bi bi-cart3 me-2"></i><?= h(tr('سلة المشتريات', 'Shopping Cart')) ?></h5>
|
||||
<span class="badge bg-light text-primary rounded-pill px-2" id="cartBadgeCount">0</span>
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-light border-bottom">
|
||||
<select class="form-select border-0 shadow-sm rounded-3 mb-2" id="posBranch" <?= count($allowedBranches) === 1 ? 'disabled' : '' ?> required>
|
||||
<?php foreach ($allowedBranches as $branchCode):
|
||||
$branchLabel = h(branch_label($branchCode));
|
||||
?>
|
||||
<option value="<?= h($branchCode) ?>"><?= $branchLabel ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<input type="text" id="posCustomer" class="form-control border-0 shadow-sm rounded-3" placeholder="<?= h(tr('اسم العميل (اختياري)', 'Customer Name (Optional)')) ?>">
|
||||
</div>
|
||||
|
||||
<div class="cart-items" id="cartItemsList">
|
||||
<!-- Items rendered via JS -->
|
||||
<div class="text-center text-muted mt-5 pt-4">
|
||||
<i class="bi bi-basket2 fs-1 d-block mb-3 opacity-50"></i>
|
||||
<p><?= h(tr('السلة فارغة، اختر بعض الأصناف.', 'Cart is empty, pick some items.')) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cart-footer">
|
||||
<div class="summary-row">
|
||||
<span><?= h(tr('المجموع الفرعي', 'Subtotal')) ?></span>
|
||||
<span id="cartSubtotalVal">0.000</span>
|
||||
</div>
|
||||
<div class="summary-total">
|
||||
<span><?= h(tr('الإجمالي', 'Total')) ?></span>
|
||||
<span class="text-primary" id="cartTotalVal">0.000</span>
|
||||
</div>
|
||||
|
||||
<div class="action-grid">
|
||||
<button class="btn btn-outline-warning rounded-3 fw-semibold" onclick="holdCart()" id="btnHold" disabled>
|
||||
<i class="bi bi-pause-circle"></i> <?= h(tr('تعليق', 'Hold')) ?>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger rounded-3 fw-semibold" onclick="clearCart()" id="btnClear" disabled>
|
||||
<i class="bi bi-trash"></i> <?= h(tr('إلغاء', 'Clear')) ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="btn w-100 btn-pay" id="btnPay" onclick="openPaymentModal()" disabled>
|
||||
<i class="bi bi-wallet2 me-2"></i> <?= h(tr('دفع وإصدار', 'Pay & Checkout')) ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Form for Submission -->
|
||||
<form method="POST" id="checkoutForm" style="display: none;">
|
||||
<input type="hidden" name="branch_code" id="inputBranch">
|
||||
<input type="hidden" name="customer_name" id="inputCustomer">
|
||||
<input type="hidden" name="payment_method" id="inputPayment">
|
||||
<input type="hidden" name="notes" id="inputNotes">
|
||||
<input type="hidden" name="cart_json" id="inputCart">
|
||||
</form>
|
||||
|
||||
<!-- Payment Modal -->
|
||||
<div class="modal fade" id="paymentModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
<div class="modal-content border-0 shadow-lg" style="border-radius: 16px;">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold"><?= h(tr('إتمام الدفع', 'Complete Payment')) ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center pt-2">
|
||||
<h2 class="text-primary fw-bold mb-4" id="modalTotalAmount">0.000</h2>
|
||||
|
||||
<div class="d-grid gap-3">
|
||||
<button class="btn btn-lg btn-outline-success rounded-pill fw-semibold" onclick="submitSale('cash')">
|
||||
<i class="bi bi-cash-coin me-2"></i> <?= h(tr('نقداً', 'Cash')) ?>
|
||||
</button>
|
||||
<button class="btn btn-lg btn-outline-primary rounded-pill fw-semibold" onclick="submitSale('card')">
|
||||
<i class="bi bi-credit-card me-2"></i> <?= h(tr('بطاقة', 'Card')) ?>
|
||||
</button>
|
||||
<button class="btn btn-lg btn-outline-secondary rounded-pill fw-semibold" onclick="submitSale('transfer')">
|
||||
<i class="bi bi-phone me-2"></i> <?= h(tr('تحويل بنكي', 'Transfer')) ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Held Orders Modal -->
|
||||
<div class="modal fade" id="heldOrdersModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg" style="border-radius: 16px;">
|
||||
<div class="modal-header bg-light border-0">
|
||||
<h5 class="modal-title fw-bold"><i class="bi bi-clock-history me-2 text-primary"></i><?= h(tr('الطلبات المعلقة', 'Held Orders')) ?></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<div class="list-group list-group-flush" id="heldOrdersList">
|
||||
<!-- Rendered by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let cart = {};
|
||||
let catalogData = <?= json_encode($catalog, JSON_UNESCAPED_UNICODE) ?>;
|
||||
let currencyLabel = '<?= h(tr('ر.ع', 'OMR')) ?>';
|
||||
|
||||
// Product Grid Filtering & Searching
|
||||
function filterCat(catId) {
|
||||
document.querySelectorAll('.cat-btn').forEach(btn => btn.classList.remove('active'));
|
||||
document.querySelector(`.cat-btn[data-cat="${catId}"]`).classList.add('active');
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
document.getElementById('posSearch').addEventListener('input', applyFilters);
|
||||
|
||||
function applyFilters() {
|
||||
const q = document.getElementById('posSearch').value.toLowerCase();
|
||||
const activeCat = document.querySelector('.cat-btn.active').dataset.cat;
|
||||
|
||||
document.querySelectorAll('.product-card').forEach(card => {
|
||||
const name = card.dataset.name.toLowerCase();
|
||||
const cat = card.dataset.cat;
|
||||
|
||||
const matchesSearch = name.includes(q);
|
||||
const matchesCat = (activeCat === 'all' || cat === activeCat);
|
||||
|
||||
if (matchesSearch && matchesCat) {
|
||||
card.style.display = 'block';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Cart Logic
|
||||
function addToCart(sku) {
|
||||
if (!catalogData[sku]) return;
|
||||
|
||||
if (cart[sku]) {
|
||||
cart[sku].qty += 1;
|
||||
} else {
|
||||
cart[sku] = {
|
||||
sku: sku,
|
||||
name: document.querySelector(`.product-card[data-sku="${sku}"]`).dataset.name,
|
||||
price: parseFloat(document.querySelector(`.product-card[data-sku="${sku}"]`).dataset.price),
|
||||
qty: 1
|
||||
};
|
||||
}
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function updateQty(sku, delta) {
|
||||
if (!cart[sku]) return;
|
||||
cart[sku].qty += delta;
|
||||
if (cart[sku].qty <= 0) {
|
||||
delete cart[sku];
|
||||
}
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function renderCart() {
|
||||
const list = document.getElementById('cartItemsList');
|
||||
const badge = document.getElementById('cartBadgeCount');
|
||||
const subVal = document.getElementById('cartSubtotalVal');
|
||||
const totalVal = document.getElementById('cartTotalVal');
|
||||
const btnPay = document.getElementById('btnPay');
|
||||
const btnHold = document.getElementById('btnHold');
|
||||
const btnClear = document.getElementById('btnClear');
|
||||
|
||||
list.innerHTML = '';
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
|
||||
const skus = Object.keys(cart);
|
||||
if (skus.length === 0) {
|
||||
list.innerHTML = `
|
||||
<div class="text-center text-muted mt-5 pt-4">
|
||||
<i class="bi bi-basket2 fs-1 d-block mb-3 opacity-50"></i>
|
||||
<p><?= h(tr('السلة فارغة، اختر بعض الأصناف.', 'Cart is empty, pick some items.')) ?></p>
|
||||
</div>
|
||||
`;
|
||||
badge.innerText = '0';
|
||||
subVal.innerText = `0.000 ${currencyLabel}`;
|
||||
totalVal.innerText = `0.000 ${currencyLabel}`;
|
||||
btnPay.disabled = true;
|
||||
btnHold.disabled = true;
|
||||
btnClear.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
skus.forEach(sku => {
|
||||
const item = cart[sku];
|
||||
const lineTotal = item.price * item.qty;
|
||||
total += lineTotal;
|
||||
count += item.qty;
|
||||
|
||||
list.innerHTML += `
|
||||
<div class="cart-item">
|
||||
<div class="cart-item-info">
|
||||
<div class="cart-item-title">
|
||||
${item.name}
|
||||
</div>
|
||||
<div class="cart-item-price">
|
||||
${item.price.toFixed(3)} ${currencyLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div class="cart-item-controls">
|
||||
<button class="cart-btn" onclick="updateQty('${sku}', -1)"><i class="bi bi-dash"></i></button>
|
||||
<span class="cart-qty">${item.qty}</span>
|
||||
<button class="cart-btn" onclick="updateQty('${sku}', 1)"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
badge.innerText = count;
|
||||
const totalStr = `${total.toFixed(3)} ${currencyLabel}`;
|
||||
subVal.innerText = totalStr;
|
||||
totalVal.innerText = totalStr;
|
||||
document.getElementById('modalTotalAmount').innerText = totalStr;
|
||||
|
||||
btnPay.disabled = false;
|
||||
btnHold.disabled = false;
|
||||
btnClear.disabled = false;
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
cart = {};
|
||||
document.getElementById('posCustomer').value = '';
|
||||
renderCart();
|
||||
}
|
||||
|
||||
// Payment & Checkout
|
||||
let paymentModalObj = null;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
paymentModalObj = new bootstrap.Modal(document.getElementById('paymentModal'));
|
||||
updateHeldOrdersCount();
|
||||
});
|
||||
|
||||
function openPaymentModal() {
|
||||
if (Object.keys(cart).length === 0) return;
|
||||
paymentModalObj.show();
|
||||
}
|
||||
|
||||
function submitSale(method) {
|
||||
const branch = document.getElementById('posBranch').value || '<?= h($allowedBranches[0] ?? '') ?>';
|
||||
const customer = document.getElementById('posCustomer').value;
|
||||
|
||||
const itemsArr = Object.values(cart).map(item => ({
|
||||
sku: item.sku,
|
||||
qty: item.qty
|
||||
}));
|
||||
|
||||
document.getElementById('inputBranch').value = branch;
|
||||
document.getElementById('inputCustomer').value = customer;
|
||||
document.getElementById('inputPayment').value = method;
|
||||
document.getElementById('inputCart').value = JSON.stringify(itemsArr);
|
||||
|
||||
document.getElementById('checkoutForm').submit();
|
||||
}
|
||||
|
||||
// Held Orders Logic (localStorage)
|
||||
function getHeldOrders() {
|
||||
const raw = localStorage.getItem('posHeldOrders');
|
||||
return raw ? JSON.parse(raw) : [];
|
||||
}
|
||||
|
||||
function saveHeldOrders(orders) {
|
||||
localStorage.setItem('posHeldOrders', JSON.stringify(orders));
|
||||
updateHeldOrdersCount();
|
||||
}
|
||||
|
||||
function updateHeldOrdersCount() {
|
||||
const orders = getHeldOrders();
|
||||
document.getElementById('heldOrdersCount').innerText = orders.length;
|
||||
}
|
||||
|
||||
function holdCart() {
|
||||
if (Object.keys(cart).length === 0) return;
|
||||
|
||||
const orders = getHeldOrders();
|
||||
const orderName = document.getElementById('posCustomer').value || `<?= h(tr('عميل غير معروف', 'Unknown')) ?> #${Math.floor(Math.random()*1000)}`;
|
||||
const branch = document.getElementById('posBranch').value;
|
||||
|
||||
const newOrder = {
|
||||
id: Date.now(),
|
||||
name: orderName,
|
||||
branch: branch,
|
||||
cart: JSON.parse(JSON.stringify(cart)),
|
||||
time: new Date().toLocaleTimeString()
|
||||
};
|
||||
|
||||
orders.push(newOrder);
|
||||
saveHeldOrders(orders);
|
||||
|
||||
// Clear current cart via toast
|
||||
clearCart();
|
||||
|
||||
const Toast = Swal.mixin({
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 2000
|
||||
});
|
||||
Toast.fire({
|
||||
icon: 'success',
|
||||
title: '<?= h(tr('تم تعليق الطلب', 'Order held successfully')) ?>'
|
||||
});
|
||||
}
|
||||
|
||||
let heldOrdersModalObj = null;
|
||||
function openHeldOrdersModal() {
|
||||
if (!heldOrdersModalObj) {
|
||||
heldOrdersModalObj = new bootstrap.Modal(document.getElementById('heldOrdersModal'));
|
||||
}
|
||||
|
||||
const list = document.getElementById('heldOrdersList');
|
||||
const orders = getHeldOrders();
|
||||
|
||||
list.innerHTML = '';
|
||||
if (orders.length === 0) {
|
||||
list.innerHTML = `<div class="p-4 text-center text-muted"><?= h(tr('لا توجد طلبات معلقة', 'No held orders')) ?></div>`;
|
||||
} else {
|
||||
orders.forEach((order, index) => {
|
||||
const count = Object.values(order.cart).reduce((sum, i) => sum + i.qty, 0);
|
||||
list.innerHTML += `
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center p-3">
|
||||
<div>
|
||||
<h6 class="mb-1 fw-bold">
|
||||
${order.name}
|
||||
</h6>
|
||||
<small class="text-muted"><i class="bi bi-clock me-1"></i>${order.time} • ${count} <?= h(tr('قطعة', 'items')) ?></small>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-pill px-3" onclick="resumeOrder(${index})"><?= h(tr('استعادة', 'Resume')) ?></button>
|
||||
<button class="btn btn-sm btn-outline-danger rounded-pill" onclick="deleteHeldOrder(${index})"><i class="bi bi-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
heldOrdersModalObj.show();
|
||||
}
|
||||
|
||||
function resumeOrder(index) {
|
||||
const orders = getHeldOrders();
|
||||
if (!orders[index]) return;
|
||||
|
||||
const order = orders[index];
|
||||
|
||||
// Warn if cart is not empty
|
||||
if (Object.keys(cart).length > 0) {
|
||||
if (!confirm('<?= h(tr('السلة الحالية غير فارغة، هل تريد استبدالها؟', 'Current cart is not empty, replace it?')) ?>')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cart = order.cart;
|
||||
document.getElementById('posCustomer').value = order.name.includes('#') ? '' : order.name;
|
||||
if (document.getElementById('posBranch').querySelector(`option[value="${order.branch}"]`)) {
|
||||
document.getElementById('posBranch').value = order.branch;
|
||||
}
|
||||
|
||||
orders.splice(index, 1);
|
||||
saveHeldOrders(orders);
|
||||
|
||||
renderCart();
|
||||
heldOrdersModalObj.hide();
|
||||
}
|
||||
|
||||
function deleteHeldOrder(index) {
|
||||
const orders = getHeldOrders();
|
||||
orders.splice(index, 1);
|
||||
saveHeldOrders(orders);
|
||||
openHeldOrdersModal(); // re-render
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
@ -55,19 +55,19 @@ require __DIR__ . '/includes/header.php';
|
||||
</section>
|
||||
|
||||
<section class="surface-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead>
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th><?= h(tr('المورد', 'Supplier')) ?></th>
|
||||
<th><?= h(tr('المرجع', 'Reference')) ?></th>
|
||||
<th><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th><?= h(tr('الحالة', 'Status')) ?></th>
|
||||
<th><?= h(tr('الوصول المتوقع', 'ETA')) ?></th>
|
||||
<th><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المورد', 'Supplier')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المرجع', 'Reference')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الحالة', 'Status')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الوصول المتوقع', 'ETA')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php if(empty($purchaseRows)): ?>
|
||||
<tr><td colspan="6" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
@ -79,10 +79,10 @@ require __DIR__ . '/includes/header.php';
|
||||
<td><span class="badge text-bg-light border"><?= h($row['status']) ?></span></td>
|
||||
<td><?= h($row['eta']) ?></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-light text-primary border" onclick="mockEdit()" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="mockEdit()" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light text-danger border" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
20
sale.php
20
sale.php
@ -42,17 +42,17 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="col-md-3"><div class="detail-card"><span><?= h(tr('الكاشير', 'Cashier')) ?></span><strong><?= h((string) $sale['cashier_name']) ?></strong></div></div>
|
||||
<div class="col-md-3"><div class="detail-card"><span><?= h(tr('الإجمالي', 'Total')) ?></span><strong><?= h(currency((float) $sale['total_amount'])) ?></strong></div></div>
|
||||
</div>
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= h(tr('الصنف', 'Item')) ?></th>
|
||||
<th><?= h(tr('الكمية', 'Qty')) ?></th>
|
||||
<th><?= h(tr('السعر', 'Price')) ?></th>
|
||||
<th><?= h(tr('الإجمالي', 'Line total')) ?></th>
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الصنف', 'Item')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الكمية', 'Qty')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('السعر', 'Price')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الإجمالي', 'Line total')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php foreach ($sale['items'] as $item): ?>
|
||||
<tr>
|
||||
<td><?= h(current_lang() === 'ar' ? ($item['name_ar'] ?? $item['sku']) : ($item['name_en'] ?? $item['sku'])) ?></td>
|
||||
@ -63,7 +63,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($sale['notes'])): ?>
|
||||
<div class="alert alert-light border mb-0"><strong><?= h(tr('ملاحظات:', 'Notes:')) ?></strong> <?= h((string) $sale['notes']) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
30
sales.php
30
sales.php
@ -100,20 +100,20 @@ require __DIR__ . '/includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= h(tr('الإيصال', 'Receipt')) ?></th>
|
||||
<th><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th><?= h(tr('النوع', 'Type')) ?></th>
|
||||
<th><?= h(tr('الكاشير', 'Cashier')) ?></th>
|
||||
<th><?= h(tr('الإجمالي', 'Total')) ?></th>
|
||||
<th><?= h(tr('التاريخ', 'Date')) ?></th>
|
||||
<th><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الإيصال', 'Receipt')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('النوع', 'Type')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الكاشير', 'Cashier')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الإجمالي', 'Total')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('التاريخ', 'Date')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php foreach ($sales as $sale): ?>
|
||||
<tr>
|
||||
<td>
|
||||
@ -129,10 +129,10 @@ require __DIR__ . '/includes/header.php';
|
||||
<a class="btn btn-sm btn-light text-primary border me-1" href="<?= h(url_for('sale.php', ['id' => $sale['id']])) ?>" title="<?= h(tr('تفاصيل', 'Detail')) ?>">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-light text-secondary border me-1" onclick="mockEdit()" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="mockEdit()" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light text-danger border" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
@ -140,7 +140,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<nav class="mt-4">
|
||||
|
||||
297
stock.php
297
stock.php
@ -5,6 +5,93 @@ $pageTitle = tr('المخزون', 'Stock');
|
||||
$activeNav = 'stock';
|
||||
$dbError = null;
|
||||
|
||||
// Handle AJAX actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
header('Content-Type: application/json');
|
||||
$pdo = db();
|
||||
|
||||
if ($_POST['action'] === 'save') {
|
||||
try {
|
||||
$sku = $_POST['sku'] ?? '';
|
||||
$name = $_POST['name'] ?? '';
|
||||
$price = (float)($_POST['price'] ?? 0);
|
||||
$base_stock = (int)($_POST['base_stock'] ?? 0);
|
||||
$vat = (float)($_POST['vat'] ?? 5);
|
||||
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
|
||||
$supplier_id = !empty($_POST['supplier_id']) ? (int)$_POST['supplier_id'] : null;
|
||||
|
||||
if (!$sku || !$name) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing SKU or Name']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$image_url = $_POST['existing_image_url'] ?? null;
|
||||
if (isset($_FILES['picture']) && $_FILES['picture']['error'] === UPLOAD_ERR_OK) {
|
||||
$uploadDir = __DIR__ . '/assets/images/items/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0775, true);
|
||||
}
|
||||
$ext = pathinfo($_FILES['picture']['name'], PATHINFO_EXTENSION);
|
||||
$filename = time() . '_' . rand(1000, 9999) . '.' . $ext;
|
||||
if (move_uploaded_file($_FILES['picture']['tmp_name'], $uploadDir . $filename)) {
|
||||
$image_url = 'assets/images/items/' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT id FROM items WHERE sku = ?');
|
||||
$stmt->execute([$sku]);
|
||||
$existing = $stmt->fetch();
|
||||
|
||||
if (isset($_POST['original_sku']) && $_POST['original_sku'] !== '') {
|
||||
$orig_sku = $_POST['original_sku'];
|
||||
if ($existing && $existing['id'] != ($pdo->query("SELECT id FROM items WHERE sku = " . $pdo->quote($orig_sku))->fetchColumn() ?: -1)) {
|
||||
echo json_encode(['success' => false, 'error' => 'SKU already exists']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE items SET sku=?, name=?, price=?, base_stock=?, vat=?, category_id=?, supplier_id=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
|
||||
$params = [$sku, $name, $price, $base_stock, $vat, $category_id, $supplier_id];
|
||||
if ($image_url) {
|
||||
$params[] = $image_url;
|
||||
}
|
||||
$params[] = $orig_sku;
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
} else {
|
||||
if ($existing) {
|
||||
echo json_encode(['success' => false, 'error' => 'SKU already exists']);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, base_stock, vat, category_id, supplier_id, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$sku, $name, $price, $base_stock, $vat, $category_id, $supplier_id, $image_url]);
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'delete') {
|
||||
try {
|
||||
$sku = $_POST['sku'] ?? '';
|
||||
if (!$sku) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing SKU']);
|
||||
exit;
|
||||
}
|
||||
$stmt = $pdo->prepare('DELETE FROM items WHERE sku = ?');
|
||||
$stmt->execute([$sku]);
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allStock = [];
|
||||
try {
|
||||
$allStock = stock_snapshot();
|
||||
@ -56,10 +143,10 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="row g-4 align-items-center mb-3">
|
||||
<div class="col-lg-8">
|
||||
<h3 class="h5 mb-1"><i class="bi bi-box-seam me-2"></i><?= h(tr('قائمة الأصناف والمخزون', 'Items & Stock List')) ?></h3>
|
||||
<p class="text-muted mb-0"><?= h(tr('احتساب مبسط = الرصيد الابتدائي - الكميات المباعة في هذا الإصدار الأول.', 'Starter calculation = opening stock minus sold quantities in this first version.')) ?></p>
|
||||
<p class="text-muted mb-0"><?= h(tr('إدارة الأصناف وجرد المخزون.', 'Manage items and inventory.')) ?></p>
|
||||
</div>
|
||||
<div class="col-lg-4 text-lg-end">
|
||||
<button type="button" class="btn btn-primary" onclick="openItemModal()">
|
||||
<button type="button" class="btn btn-primary shadow-sm" onclick="openItemModal()">
|
||||
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة صنف', 'Add Item')) ?>
|
||||
</button>
|
||||
</div>
|
||||
@ -68,7 +155,12 @@ require __DIR__ . '/includes/header.php';
|
||||
<form class="mb-4" method="GET" action="stock.php">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث برمز الصنف أو الاسم...', 'Search by SKU or name...')) ?>" value="<?= h($search) ?>">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" id="searchInput" class="form-control" placeholder="<?= h(tr('بحث برمز الصنف أو الاسم...', 'Search by SKU or name...')) ?>" value="<?= h($search) ?>" autocomplete="off">
|
||||
<button class="btn btn-outline-secondary bg-white" type="button" id="clearSearchBtn" style="<?= empty($search) ? 'display: none;' : '' ?>" title="<?= h(tr('مسح البحث', 'Clear Search')) ?>">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select name="category" class="form-select">
|
||||
@ -87,7 +179,7 @@ require __DIR__ . '/includes/header.php';
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-primary w-100" type="submit">
|
||||
<button class="btn btn-primary w-100 shadow-sm" type="submit">
|
||||
<i class="bi bi-funnel"></i> <?= h(tr('تصفية', 'Filter')) ?>
|
||||
</button>
|
||||
</div>
|
||||
@ -99,45 +191,54 @@ require __DIR__ . '/includes/header.php';
|
||||
<?php if ($dbError): ?>
|
||||
<div class="alert alert-warning"><?= h($dbError) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead class="table-primary">
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th width="60"><?= h(tr('صورة', 'Pic')) ?></th>
|
||||
<th><?= h(tr('الصنف', 'Product')) ?></th>
|
||||
<th><?= h(tr('السعر', 'Price')) ?></th>
|
||||
<th><?= h(tr('افتتاحي', 'Opening')) ?></th>
|
||||
<th><?= h(tr('مباع', 'Sold')) ?></th>
|
||||
<th><?= h(tr('متاح', 'Available')) ?></th>
|
||||
<th><?= h(tr('التنبيه', 'Signal')) ?></th>
|
||||
<th class="text-end"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent">SKU</th>
|
||||
<th width="70" class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('صورة', 'Pic')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent text-start"><?= h(tr('الصنف', 'Product')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('السعر', 'Price')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('افتتاحي', 'Opening')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('مباع', 'Sold')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('متاح', 'Available')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('التنبيه', 'Signal')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent text-end pe-4"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php if(empty($stockRows)): ?>
|
||||
<tr><td colspan="8" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<tr><td colspan="9" class="text-center text-muted py-5"><i class="bi bi-inbox fs-1 d-block text-black-50 mb-2"></i> <?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($stockRows as $row): ?>
|
||||
<tr>
|
||||
<td><?= h($row['sku']) ?></td>
|
||||
<td><?= h($row['name']) ?></td>
|
||||
<td><?= h(currency($row['price'])) ?></td>
|
||||
<td><?= h((string) $row['base_stock']) ?></td>
|
||||
<td><?= h((string) $row['sold']) ?></td>
|
||||
<td class="fw-semibold"><?= h((string) $row['available']) ?></td>
|
||||
<tr style="transition: all 0.2s ease;">
|
||||
<td class="text-secondary"><?= h($row['sku']) ?></td>
|
||||
<td>
|
||||
<?php if ($row['available'] <= 12): ?>
|
||||
<span class="badge text-bg-warning"><?= h(tr('منخفض', 'Low')) ?></span>
|
||||
<?php if (!empty($row['image_url'])): ?>
|
||||
<img src="<?= h($row['image_url']) ?>" alt="pic" class="img-thumbnail p-0 shadow-sm" style="width: 45px; height: 45px; object-fit: cover; border-radius: 10px;">
|
||||
<?php else: ?>
|
||||
<span class="badge text-bg-light border"><?= h(tr('مستقر', 'Stable')) ?></span>
|
||||
<div class="bg-light text-muted d-flex align-items-center justify-content-center shadow-sm mx-auto" style="width: 45px; height: 45px; border-radius: 10px; font-size: 1rem;">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button class="btn btn-sm btn-light text-primary border" onclick="openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? 5) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<td class="fw-bold text-start text-dark"><?= h($row['name']) ?></td>
|
||||
<td><span class="badge bg-light text-dark border px-2 py-1"><?= h(currency($row['price'])) ?></span></td>
|
||||
<td class="text-secondary"><?= h((string) $row['base_stock']) ?></td>
|
||||
<td class="text-secondary"><?= h((string) $row['sold']) ?></td>
|
||||
<td class="fw-bold text-primary fs-6"><?= h((string) $row['available']) ?></td>
|
||||
<td>
|
||||
<?php if ($row['available'] <= 12): ?>
|
||||
<span class="badge bg-warning bg-opacity-25 text-dark px-3 py-2 rounded-pill"><i class="bi bi-exclamation-triangle me-1"></i> <?= h(tr('منخفض', 'Low')) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-success bg-opacity-10 text-success px-3 py-2 rounded-pill border border-success border-opacity-25"><i class="bi bi-check2-circle me-1"></i> <?= h(tr('مستقر', 'Stable')) ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? 5) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light text-danger border" onclick="mockDelete()" data-bs-toggle="tooltip" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="deleteItem('<?= h(addslashes($row['sku'])) ?>')" data-bs-toggle="tooltip" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
@ -165,12 +266,14 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="modal fade" id="itemModal" tabindex="-1" aria-labelledby="itemModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form onsubmit="handleItemSubmit(event)">
|
||||
<form onsubmit="handleItemSubmit(event)" id="itemForm">
|
||||
<div class="modal-header bg-primary text-white ">
|
||||
<h5 class="modal-title" id="itemModalLabel"><?= h(tr('إضافة / تعديل صنف', 'Add / Edit Item')) ?></h5>
|
||||
<button type="button" class="btn-close btn-close-white " data-bs-dismiss="modal" aria-label="Close" ></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="item_original_sku">
|
||||
<input type="hidden" id="item_existing_image_url">
|
||||
<div class="mb-3 text-center">
|
||||
<label class="form-label d-block text-start"><?= h(tr('صورة الصنف', 'Item Picture')) ?></label>
|
||||
<input type="file" class="form-control" id="item_picture" accept="image/*">
|
||||
@ -178,7 +281,7 @@ require __DIR__ . '/includes/header.php';
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('رمز الصنف (SKU)', 'SKU')) ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="item_sku" required maxlength="8">
|
||||
<input type="text" class="form-control" id="item_sku" required maxlength="15">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="suggestSKU()" title="<?= h(tr('اقتراح رمز', 'Suggest SKU')) ?>">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
@ -204,7 +307,7 @@ require __DIR__ . '/includes/header.php';
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('التصنيف', 'Category')) ?></label>
|
||||
<select class="form-select" id="item_category" required>
|
||||
<select class="form-select" id="item_category">
|
||||
<option value=""><?= h(tr('-- اختر التصنيف --', '-- Select Category --')) ?></option>
|
||||
<?php foreach($categories as $cat): ?>
|
||||
<option value="<?= h($cat['id']) ?>"><?= h(current_lang() === 'ar' ? $cat['name_ar'] : $cat['name_en']) ?></option>
|
||||
@ -213,7 +316,7 @@ require __DIR__ . '/includes/header.php';
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= h(tr('المورد', 'Supplier')) ?></label>
|
||||
<select class="form-select" id="item_supplier" required>
|
||||
<select class="form-select" id="item_supplier">
|
||||
<option value=""><?= h(tr('-- اختر المورد --', '-- Select Supplier --')) ?></option>
|
||||
<?php foreach($suppliers as $sup): ?>
|
||||
<option value="<?= h($sup['id']) ?>"><?= h($sup['name']) ?></option>
|
||||
@ -223,7 +326,9 @@ require __DIR__ . '/includes/header.php';
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
|
||||
<button type="submit" class="btn btn-primary"><?= h(tr('حفظ التغييرات', 'Save Changes')) ?></button>
|
||||
<button type="submit" class="btn btn-primary" id="saveItemBtn">
|
||||
<i class="bi bi-save me-1"></i> <?= h(tr('حفظ التغييرات', 'Save Changes')) ?>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -250,9 +355,47 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
|
||||
itemModalObj = new bootstrap.Modal(document.getElementById('itemModal'));
|
||||
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const clearSearchBtn = document.getElementById('clearSearchBtn');
|
||||
let searchTimeout;
|
||||
|
||||
if (searchInput) {
|
||||
if (searchInput.value.length > 0) {
|
||||
searchInput.focus();
|
||||
const valLen = searchInput.value.length;
|
||||
searchInput.setSelectionRange(valLen, valLen);
|
||||
}
|
||||
|
||||
searchInput.addEventListener('input', function() {
|
||||
const val = this.value;
|
||||
if (clearSearchBtn) {
|
||||
clearSearchBtn.style.display = val.length > 0 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
const trimmed = val.trim();
|
||||
if (trimmed.length >= 2 || trimmed.length === 0) {
|
||||
this.closest('form').submit();
|
||||
}
|
||||
}, 600);
|
||||
});
|
||||
}
|
||||
|
||||
if (clearSearchBtn) {
|
||||
clearSearchBtn.addEventListener('click', function() {
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
searchInput.closest('form').submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function openItemModal(sku = '', name = '', price = '', base_stock = '', vat = '5', category_id = '', supplier_id = '', image_url = '') {
|
||||
document.getElementById('item_original_sku').value = sku;
|
||||
document.getElementById('item_existing_image_url').value = image_url;
|
||||
document.getElementById('item_sku').value = sku;
|
||||
document.getElementById('item_name').value = name;
|
||||
document.getElementById('item_price').value = price;
|
||||
@ -270,27 +413,61 @@ function openItemModal(sku = '', name = '', price = '', base_stock = '', vat = '
|
||||
preview.id = 'image_preview';
|
||||
preview.src = image_url;
|
||||
preview.style.maxHeight = '100px';
|
||||
preview.className = 'mt-2 rounded';
|
||||
preview.className = 'mt-2 rounded shadow-sm border';
|
||||
document.getElementById('item_picture').parentElement.appendChild(preview);
|
||||
}
|
||||
document.getElementById('item_picture').value = '';
|
||||
|
||||
|
||||
itemModalObj.show();
|
||||
}
|
||||
|
||||
function handleItemSubmit(e) {
|
||||
async function handleItemSubmit(e) {
|
||||
e.preventDefault();
|
||||
itemModalObj.hide();
|
||||
Swal.fire({
|
||||
title: '<?= h(tr('تم الحفظ', 'Saved')) ?>',
|
||||
text: '<?= h(tr('البيانات الحالية مخزنة كنسخة تجريبية، سيتم تفعيل الحفظ الفعلي لاحقاً.', 'Current data is a starter mock, real saving will be enabled later.')) ?>',
|
||||
icon: 'success',
|
||||
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>'
|
||||
});
|
||||
const btn = document.getElementById('saveItemBtn');
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
|
||||
btn.disabled = true;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'save');
|
||||
formData.append('original_sku', document.getElementById('item_original_sku').value);
|
||||
formData.append('sku', document.getElementById('item_sku').value);
|
||||
formData.append('name', document.getElementById('item_name').value);
|
||||
formData.append('price', document.getElementById('item_price').value);
|
||||
formData.append('base_stock', document.getElementById('item_base_stock').value);
|
||||
formData.append('vat', document.getElementById('item_vat').value);
|
||||
formData.append('category_id', document.getElementById('item_category').value);
|
||||
formData.append('supplier_id', document.getElementById('item_supplier').value);
|
||||
formData.append('existing_image_url', document.getElementById('item_existing_image_url').value);
|
||||
|
||||
const picInput = document.getElementById('item_picture');
|
||||
if (picInput.files.length > 0) {
|
||||
formData.append('picture', picInput.files[0]);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('stock.php', { method: 'POST', body: formData });
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
itemModalObj.hide();
|
||||
Swal.fire({
|
||||
title: '<?= h(tr('تم الحفظ بنجاح', 'Successfully saved')) ?>',
|
||||
icon: 'success',
|
||||
showConfirmButton: false,
|
||||
timer: 1500
|
||||
}).then(() => location.reload());
|
||||
} else {
|
||||
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', json.error || '<?= h(tr('فشل الحفظ', 'Failed to save')) ?>', 'error');
|
||||
}
|
||||
} catch(err) {
|
||||
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', '<?= h(tr('حدث خطأ في الاتصال', 'Network error')) ?>', 'error');
|
||||
} finally {
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function mockDelete() {
|
||||
function deleteItem(sku) {
|
||||
Swal.fire({
|
||||
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
|
||||
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
|
||||
@ -300,13 +477,27 @@ function mockDelete() {
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
|
||||
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
|
||||
}).then((result) => {
|
||||
}).then(async (result) => {
|
||||
if (result.isConfirmed) {
|
||||
Swal.fire(
|
||||
'<?= h(tr('محذوف!', 'Deleted!')) ?>',
|
||||
'<?= h(tr('حساب تجريبي لا يمكن حذفه.', 'Demo data cannot be deleted.')) ?>',
|
||||
'success'
|
||||
);
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'delete');
|
||||
formData.append('sku', sku);
|
||||
const res = await fetch('stock.php', { method: 'POST', body: formData });
|
||||
const json = await res.json();
|
||||
if (json.success) {
|
||||
Swal.fire({
|
||||
title: '<?= h(tr('محذوف!', 'Deleted!')) ?>',
|
||||
icon: 'success',
|
||||
showConfirmButton: false,
|
||||
timer: 1500
|
||||
}).then(() => location.reload());
|
||||
} else {
|
||||
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', json.error || '<?= h(tr('فشل الحذف', 'Failed to delete')) ?>', 'error');
|
||||
}
|
||||
} catch(err) {
|
||||
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', '<?= h(tr('حدث خطأ في الاتصال', 'Network error')) ?>', 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -75,19 +75,19 @@ require __DIR__ . '/includes/header.php';
|
||||
</section>
|
||||
|
||||
<section class="surface-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead>
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th><?= h(tr('الاسم', 'Name')) ?></th>
|
||||
<th><?= h(tr('المسؤول', 'Contact Person')) ?></th>
|
||||
<th><?= h(tr('الهاتف', 'Phone')) ?></th>
|
||||
<th><?= h(tr('البريد', 'Email')) ?></th>
|
||||
<th><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent">ID</th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الاسم', 'Name')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المسؤول', 'Contact Person')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الهاتف', 'Phone')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('البريد', 'Email')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php if(empty($items)): ?>
|
||||
<tr><td colspan="6" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
@ -99,10 +99,10 @@ require __DIR__ . '/includes/header.php';
|
||||
<td><?= h($item['phone']) ?></td>
|
||||
<td><?= h($item['email']) ?></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-light text-primary border" onclick="editItem(<?= htmlspecialchars(json_encode($item)) ?>)" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="editItem(<?= htmlspecialchars(json_encode($item)) ?>)" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light text-danger border" onclick="deleteItem(<?= $item['id'] ?>)" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="deleteItem(<?= $item['id'] ?>)" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
26
users.php
26
users.php
@ -54,20 +54,20 @@ require __DIR__ . '/includes/header.php';
|
||||
</section>
|
||||
|
||||
<section class="surface-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table app-table align-middle mb-0">
|
||||
<thead>
|
||||
<div class="table-responsive shadow-sm" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.05);">
|
||||
<table class="table table-hover align-middle mb-0 text-center" style="background-color: #fff;">
|
||||
<thead style="background: linear-gradient(90deg, #0d6efd, #0dcaf0);">
|
||||
<tr>
|
||||
<th><?= h(tr('المستخدم', 'User')) ?></th>
|
||||
<th><?= h(tr('الدور', 'Role')) ?></th>
|
||||
<th><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th>POS</th>
|
||||
<th><?= h(tr('تقارير', 'Reports')) ?></th>
|
||||
<th><?= h(tr('مستخدمون', 'Users')) ?></th>
|
||||
<th><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('المستخدم', 'User')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الدور', 'Role')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('الفرع', 'Branch')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent">POS</th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('تقارير', 'Reports')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('مستخدمون', 'Users')) ?></th>
|
||||
<th class="text-white border-0 py-3 fw-semibold bg-transparent"><?= h(tr('إجراءات', 'Actions')) ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="border-top-0">
|
||||
<?php if(empty($accounts)): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
|
||||
<?php endif; ?>
|
||||
@ -83,10 +83,10 @@ require __DIR__ . '/includes/header.php';
|
||||
<td><span class="badge <?= in_array($account['role'], ['owner', 'manager'], true) ? 'text-bg-light border' : 'text-bg-secondary' ?>"><?= h(in_array($account['role'], ['owner', 'manager'], true) ? tr('نعم', 'Yes') : tr('لا', 'No')) ?></span></td>
|
||||
<td><span class="badge <?= $account['role'] === 'owner' ? 'text-bg-light border' : 'text-bg-secondary' ?>"><?= h($account['role'] === 'owner' ? tr('نعم', 'Yes') : tr('لا', 'No')) ?></span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-light text-primary border" onclick="mockEdit()" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<button class="btn btn-sm btn-outline-primary rounded-circle shadow-sm" style="width: 34px; height: 34px; padding: 0;" onclick="mockEdit()" title="<?= h(tr('تعديل', 'Edit')) ?>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light text-danger border" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<button class="btn btn-sm btn-outline-danger rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user