Autosave: 20260419-160352

This commit is contained in:
Flatlogic Bot 2026-04-19 16:03:44 +00:00
parent cc0da06fbb
commit b1a016903b
13 changed files with 1354 additions and 84 deletions

97
api/place_order.php Normal file
View File

@ -0,0 +1,97 @@
<?php
require_once __DIR__ . '/../includes/app.php';
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'error' => 'Invalid method']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || empty($input['items'])) {
echo json_encode(['success' => false, 'error' => 'Cart is empty']);
exit;
}
$name = trim($input['name'] ?? '');
$phone = trim($input['phone'] ?? '');
$address = trim($input['address'] ?? '');
if ($name === '' || $phone === '' || $address === '') {
echo json_encode(['success' => false, 'error' => 'Missing customer details']);
exit;
}
$items = $input['items'];
$total = 0;
// Recalculate total for security
$db = db();
$processedItems = [];
foreach ($items as $id => $item) {
$qty = (int)$item['qty'];
if ($qty <= 0) continue;
// get price from DB
$stmt = $db->prepare("SELECT sku, name, price FROM items WHERE id = ?");
$stmt->execute([$id]);
$dbItem = $stmt->fetch();
if ($dbItem) {
$price = (float)$dbItem['price'];
$total += ($price * $qty);
$processedItems[] = [
'id' => $id,
'sku' => $dbItem['sku'],
'name' => $dbItem['name'],
'price' => $price,
'qty' => $qty
];
}
}
if (empty($processedItems)) {
echo json_encode(['success' => false, 'error' => 'Invalid items']);
exit;
}
try {
$stmt = $db->prepare("INSERT INTO online_orders (customer_name, customer_phone, customer_address, items_json, total_amount) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([
$name,
$phone,
$address,
json_encode($processedItems, JSON_UNESCAPED_UNICODE),
$total
]);
// Optional: send telegram notification if configured
try {
// require_once __DIR__ . '/telegram_webhook.php'; // wait, it might not be a function but a script. Let's just do simple notification
$orderId = $db->lastInsertId();
$msg = "🛒 *New Online Order #{$orderId}*\n\n";
$msg .= "👤 {$name}\n📞 {$phone}\n📍 {$address}\n\n";
$msg .= "💰 Total: " . currency($total) . "\n";
// To send, we'd need to call telegram api directly if token is set.
$botToken = getenv('TELEGRAM_BOT_TOKEN') ?: get_setting('telegram_bot_token');
$chatId = getenv('TELEGRAM_CHAT_ID') ?: get_setting('telegram_chat_id');
if ($botToken && $chatId) {
$url = "https://api.telegram.org/bot{$botToken}/sendMessage";
$data = ['chat_id' => $chatId, 'text' => $msg, 'parse_mode' => 'Markdown'];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$context = stream_context_create($options);
@file_get_contents($url, false, $context);
}
} catch (Exception $e) {
// ignore notification errors
}
echo json_encode(['success' => true]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Database error']);
}

View File

@ -98,6 +98,20 @@ function pull_flash(): ?array
function branches(): array
{
try {
$db = db();
$stmt = $db->query("SELECT * FROM branches");
$res = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($res) {
$arr = [];
foreach ($res as $row) {
$arr[$row['code']] = $row;
}
return $arr;
}
} catch (Exception $e) {
// Table might not exist yet
}
return [
'muscat' => ['code' => 'muscat', 'name_ar' => 'فرع مسقط', 'name_en' => 'Muscat Branch', 'city_ar' => 'مسقط', 'city_en' => 'Muscat'],
'sohar' => ['code' => 'sohar', 'name_ar' => 'فرع صحار', 'name_en' => 'Sohar Branch', 'city_ar' => 'صحار', 'city_en' => 'Sohar'],
@ -208,7 +222,7 @@ function catalog(): array
"cost_price" => (float)($item["cost_price"] ?? 0),
"base_stock" => (int)$item["base_stock"],
"vat" => (float)$item["vat"],
"category_id" => $item["category_id"],
"category_id" => $item["category_id"], "in_catalog" => (int)($item["in_catalog"] ?? 0),
"supplier_id" => $item["supplier_id"],
"image_url" => $item["image_url"],
"unit_id" => $item["unit_id"],
@ -414,6 +428,7 @@ function report_metrics(): array
$branchTotals = [];
$paymentTotals = [];
$productTotals = [];
$monthlyTotals = [];
$gross = 0.0;
$totalVat = 0.0;
@ -423,7 +438,10 @@ function report_metrics(): array
$payment = $sale['payment_method'];
$paymentTotals[$payment] = ($paymentTotals[$payment] ?? 0.0) + (float) $sale['total_amount'];
$gross += (float) $sale['total_amount'];
$totalVat += (float) $sale['vat_amount'];
$totalVat += (float) $sale['vat_amount'];
$month = substr((string)$sale['sale_date'], 0, 7);
$monthlyTotals[$month] = ($monthlyTotals[$month] ?? 0.0) + (float) $sale['total_amount'];
foreach ($sale['items'] as $item) {
$sku = (string) ($item['sku'] ?? '');
$qty = (int) ($item['qty'] ?? 0);
@ -433,14 +451,16 @@ function report_metrics(): array
arsort($branchTotals);
arsort($paymentTotals);
arsort($productTotals);
arsort($productTotals);
ksort($monthlyTotals);
return [
'gross' => $gross,
'total_vat' => $totalVat,
'branch_totals' => $branchTotals,
'payment_totals' => $paymentTotals,
'product_totals' => $productTotals,
'product_totals' => $productTotals,
'monthly_totals' => $monthlyTotals,
'sales_count' => count($sales),
];
}

View File

@ -6,6 +6,20 @@ $isPublic = !isset($user) || !$user;
</main>
<?php else: ?>
</div> <!-- /.container-fluid -->
<!-- App Footer -->
<footer class="bg-white border-top mt-auto py-3 px-4 text-muted small">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<div>
&copy; <?= date('Y') ?> <strong><?= h(current_lang() === 'ar' ? get_setting('company_name_ar', 'حلوى الريامي') : get_setting('company_name_en', 'Al Riyami Sweets')) ?></strong>. <?= h(tr('جميع الحقوق محفوظة.', 'All rights reserved.')) ?>
</div>
<div class="mt-2 mt-md-0">
<span class="text-secondary"><?= h(tr('تم التطوير بواسطة', 'Developed via')) ?> </span><a href="https://flatlogic.com" target="_blank" class="text-decoration-none fw-semibold">Flatlogic</a>
</div>
</div>
</footer>
<!-- /App Footer -->
</div> <!-- /#page-content-wrapper -->
</div> <!-- /#wrapper -->
<?php endif; ?>

View File

@ -11,7 +11,7 @@ $flash = pull_flash();
$assetVersion = date('YmdHi');
// Determine if we are on a public page (like login)
$isPublic = !isset($user) || !$user;
$isPublic = !empty($forcePublic) || !isset($user) || !$user;
?>
<!doctype html>
<html lang="<?= h(current_lang()) ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
@ -138,6 +138,9 @@ $isPublic = !isset($user) || !$user;
<a class="list-group-item list-group-item-action <?= $activeNav === 'suppliers' ? 'active' : '' ?>" href="<?= h(url_for('suppliers.php')) ?>">
<i class="bi bi-truck"></i> <?= h(tr('الموردون', 'Suppliers')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'online_orders' ? 'active' : '' ?>" href="<?= h(url_for('online_orders.php')) ?>">
<i class="bi bi-cart-check"></i> <?= h(tr('طلبات المتجر', 'Online Orders')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'customers' ? 'active' : '' ?>" href="<?= h(url_for('customers.php')) ?>">
<i class="bi bi-people-fill"></i> <?= h(tr('العملاء', 'Customers')) ?>
</a>
@ -153,16 +156,29 @@ $isPublic = !isset($user) || !$user;
<?php endif; ?>
<?php if (has_permission('settings', 'show')): ?>
<a class="list-group-item list-group-item-action" href="#" data-bs-toggle="modal" data-bs-target="#settingsModal">
<i class="bi bi-gear"></i> <?= h(tr('إعدادات الشركة', 'Company Settings')) ?>
</a>
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['outlets']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseSettings" role="button" aria-expanded="<?= in_array($activeNav, ['outlets']) ? 'true' : 'false' ?>" aria-controls="collapseSettings">
<div class="d-flex justify-content-between align-items-center w-100">
<span><i class="bi bi-gear"></i> <?= h(tr('الإعدادات', 'Settings')) ?></span>
<i class="bi bi-chevron-down toggle-icon" style="transition: transform 0.2s;"></i>
</div>
</a>
<div class="collapse <?= in_array($activeNav, ['outlets']) ? 'show' : '' ?>" id="collapseSettings">
<div class="list-group list-group-flush" style="background-color: rgba(0,0,0,0.15);">
<a class="list-group-item list-group-item-action" href="#" data-bs-toggle="modal" data-bs-target="#settingsModal" style="padding-left: 2.5rem; padding-right: 2.5rem;">
<i class="bi bi-dot"></i> <?= h(tr('إعدادات الشركة', 'App Settings')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'outlets' ? 'active' : '' ?>" href="<?= h(url_for('outlets.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
<i class="bi bi-dot"></i> <?= h(tr('الفروع (المنافذ)', 'Outlets')) ?>
</a>
</div>
</div>
<?php endif; ?>
</div>
</div>
<!-- /#sidebar-wrapper -->
<!-- Page Content -->
<div id="page-content-wrapper">
<div id="page-content-wrapper" class="d-flex flex-column min-vh-100">
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom top-navbar px-3">
<div class="d-flex align-items-center justify-content-between w-100">
<div class="d-flex align-items-center">
@ -171,6 +187,7 @@ $isPublic = !isset($user) || !$user;
</div>
<div class="d-flex align-items-center gap-3">
<a href="shop.php" target="_blank" class="btn btn-outline-success btn-sm me-2" title="<?= h(tr('زيارة المتجر', 'Visit Store')) ?>"><i class="bi bi-shop"></i> <span class="d-none d-md-inline"><?= h(tr('المتجر', 'Store')) ?></span></a>
<div class="language-switcher btn-group" role="group">
<a class="btn btn-sm <?= current_lang() === 'ar' ? 'btn-primary' : 'btn-outline-primary' ?>" href="<?= h(url_for(basename($_SERVER['PHP_SELF']), array_merge($_GET, ['lang' => 'ar']))) ?>">AR</a>
<a class="btn btn-sm <?= current_lang() === 'en' ? 'btn-primary' : 'btn-outline-primary' ?>" href="<?= h(url_for(basename($_SERVER['PHP_SELF']), array_merge($_GET, ['lang' => 'en']))) ?>">EN</a>

373
index.php
View File

@ -5,11 +5,41 @@ $pageTitle = tr('لوحة التحكم', 'Dashboard');
$activeNav = 'dashboard';
$dbError = null;
$metrics = ['today_sales' => 0, 'today_revenue' => 0.0, 'pos_count' => 0, 'normal_count' => 0, 'recent' => []];
$reportMetrics = ['branch_totals' => [], 'payment_totals' => [], 'product_totals' => []];
try {
$metrics = dashboard_metrics();
$reportMetrics = report_metrics();
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
$branchLabels = [];
$branchData = [];
foreach ($reportMetrics['branch_totals'] as $code => $total) {
$branchLabels[] = branch_label((string)$code);
$branchData[] = (float)$total;
}
$productLabels = [];
$productData = [];
$topProducts = array_slice($reportMetrics['product_totals'], 0, 5, true);
foreach ($topProducts as $sku => $qty) {
$productLabels[] = product_label((string)$sku);
$productData[] = (int)$qty;
}
$monthlyLabels = [];
$monthlyData = [];
if (isset($reportMetrics['monthly_totals'])) {
foreach ($reportMetrics['monthly_totals'] as $month => $total) {
// format month: "2026-04" -> "Apr 2026"
$time = strtotime($month . '-01');
$monthlyLabels[] = date('M Y', $time);
$monthlyData[] = (float)$total;
}
}
require __DIR__ . '/includes/header.php';
?>
@ -30,11 +60,11 @@ require __DIR__ . '/includes/header.php';
<!-- Metrics Row -->
<div class="row g-4 mb-4">
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-primary h-100">
<div class="card text-white bg-primary h-100 shadow-sm border-0" style="border-radius: 12px; background: linear-gradient(135deg, #0d6efd 0%, #0043a8 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start">
<h6 class="text-white-50 text-uppercase mb-0"><?= h(tr('مبيعات اليوم', 'Today sales')) ?></h6>
<i class="bi bi-receipt fs-4 text-white-50"></i>
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('مبيعات اليوم', 'Today sales')) ?></h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-receipt fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['today_sales']) ?></h2>
</div>
@ -42,11 +72,11 @@ require __DIR__ . '/includes/header.php';
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-success h-100">
<div class="card text-white bg-success h-100 shadow-sm border-0" style="border-radius: 12px; background: linear-gradient(135deg, #198754 0%, #0f5132 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start">
<h6 class="text-white-50 text-uppercase mb-0"><?= h(tr('إيراد اليوم', 'Today revenue')) ?></h6>
<i class="bi bi-cash-stack fs-4 text-white-50"></i>
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('إيراد اليوم', 'Today revenue')) ?></h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-cash-stack fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h(currency((float) $metrics['today_revenue'])) ?></h2>
</div>
@ -54,11 +84,11 @@ require __DIR__ . '/includes/header.php';
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-info h-100">
<div class="card text-white bg-info h-100 shadow-sm border-0" style="border-radius: 12px; background: linear-gradient(135deg, #0dcaf0 0%, #087990 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start">
<h6 class="text-white-50 text-uppercase mb-0">POS</h6>
<i class="bi bi-cart fs-4 text-white-50"></i>
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold">POS</h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-cart fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['pos_count']) ?></h2>
</div>
@ -66,11 +96,11 @@ require __DIR__ . '/includes/header.php';
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-warning h-100">
<div class="card text-white bg-warning h-100 shadow-sm border-0" style="border-radius: 12px; background: linear-gradient(135deg, #ffc107 0%, #b38600 100%);">
<div class="card-body d-flex flex-column justify-content-between">
<div class="d-flex justify-content-between align-items-start">
<h6 class="text-white-50 text-uppercase mb-0"><?= h(tr('بيع عادي', 'Normal sale')) ?></h6>
<i class="bi bi-basket fs-4 text-white-50"></i>
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="text-white-50 text-uppercase mb-0 fw-bold"><?= h(tr('بيع عادي', 'Normal sale')) ?></h6>
<div class="p-2 bg-white bg-opacity-25 rounded"><i class="bi bi-basket fs-5 text-white"></i></div>
</div>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['normal_count']) ?></h2>
</div>
@ -78,13 +108,55 @@ require __DIR__ . '/includes/header.php';
</div>
</div>
<!-- Charts Row -->
<div class="row g-4 mb-4">
<div class="col-xl-6">
<div class="card h-100 shadow-sm border-0" style="border-radius: 12px;">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-semibold"><i class="bi bi-bar-chart-line me-2 text-primary"></i><?= h(tr('أفضل الأصناف مبيعاً', 'Top Selling Products')) ?></h5>
</div>
<div class="card-body">
<?php if(empty($productLabels)): ?>
<div class="text-center text-muted py-5">
<i class="bi bi-graph-down text-secondary fs-1 d-block mb-3"></i>
<p><?= h(tr('لا توجد بيانات كافية لعرض الرسم البياني', 'Not enough data to show chart')) ?></p>
</div>
<?php else: ?>
<div style="position: relative; height:300px; width:100%">
<canvas id="topProductsChart"></canvas>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-xl-6">
<div class="card h-100 shadow-sm border-0" style="border-radius: 12px;">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-semibold"><i class="bi bi-pie-chart me-2 text-success"></i><?= h(tr('المبيعات حسب الفرع', 'Sales by Branch')) ?></h5>
</div>
<div class="card-body d-flex justify-content-center align-items-center">
<?php if(empty($branchLabels)): ?>
<div class="text-center text-muted py-5">
<i class="bi bi-pie-chart text-secondary fs-1 d-block mb-3"></i>
<p><?= h(tr('لا توجد بيانات كافية لعرض الرسم البياني', 'Not enough data to show chart')) ?></p>
</div>
<?php else: ?>
<div style="position: relative; height:300px; width:100%">
<canvas id="branchSalesChart"></canvas>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<!-- Recent Sales -->
<div class="col-xl-8">
<div class="card h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3">
<h5 class="mb-0 fw-semibold"><i class="bi bi-clock-history me-2"></i><?= h(tr('آخر المبيعات', 'Latest sales')) ?></h5>
<a class="btn btn-sm btn-outline-primary" href="<?= h(url_for('sales.php')) ?>"><?= h(tr('عرض الكل', 'View all')) ?></a>
<div class="card shadow-sm border-0 h-100" style="border-radius: 12px;">
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3 border-0">
<h5 class="mb-0 fw-semibold"><i class="bi bi-clock-history me-2 text-info"></i><?= h(tr('آخر المبيعات', 'Latest sales')) ?></h5>
<a class="btn btn-sm btn-outline-primary rounded-pill px-3" href="<?= h(url_for('sales.php')) ?>"><?= h(tr('عرض الكل', 'View all')) ?></a>
</div>
<div class="card-body p-0">
<?php if (!$metrics['recent']): ?>
@ -95,34 +167,42 @@ 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 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);">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0 text-center">
<thead class="bg-light text-muted">
<tr>
<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>
<th class="ps-3 border-0 py-3 fw-semibold"><?= h(tr('رقم الإيصال', 'Receipt No')) ?></th>
<th class="border-0 py-3 fw-semibold"><?= h(tr('الفرع', 'Branch')) ?></th>
<th class="border-0 py-3 fw-semibold"><?= h(tr('النوع', 'Type')) ?></th>
<th class="border-0 py-3 fw-semibold"><?= h(tr('التاريخ', 'Date')) ?></th>
<th class="text-end pe-3 border-0 py-3 fw-semibold"><?= h(tr('الإجمالي', 'Total')) ?></th>
</tr>
</thead>
<tbody class="border-top-0">
<?php foreach ($metrics['recent'] as $sale): ?>
<tr>
<td class="ps-3">
<a href="<?= h(url_for('sale.php', ['id' => $sale['id']])) ?>" class="fw-semibold text-decoration-none">
<?= h($sale['receipt_no']) ?>
<a href="<?= h(url_for('sale.php', ['id' => $sale['id']])) ?>" class="fw-bold text-decoration-none text-primary">
#<?= h($sale['receipt_no']) ?>
</a>
</td>
<td><?= h(branch_label((string) $sale['branch_code'])) ?></td>
<td><span class="badge bg-secondary"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></span></td>
<td><small class="text-muted"><?= h(date('M d, H:i', strtotime((string) $sale['sale_date']))) ?></small></td>
<td class="text-end pe-3 fw-bold"><?= h(currency((float) $sale['total_amount'])) ?></td>
<td>
<span class="badge bg-light text-dark border"><?= h(branch_label((string) $sale['branch_code'])) ?></span>
</td>
<td>
<?php if($sale['sale_mode'] === 'pos'): ?>
<span class="badge bg-info bg-opacity-10 text-info border border-info"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></span>
<?php else: ?>
<span class="badge bg-warning bg-opacity-10 text-warning border border-warning"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></span>
<?php endif; ?>
</td>
<td><small class="text-muted fw-medium"><?= h(date('M d, H:i', strtotime((string) $sale['sale_date']))) ?></small></td>
<td class="text-end pe-3 fw-bold text-success"><?= h(currency((float) $sale['total_amount'])) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
</div>
@ -130,40 +210,42 @@ require __DIR__ . '/includes/header.php';
<!-- Quick Actions / Status -->
<div class="col-xl-4">
<div class="card h-100">
<div class="card-header bg-white py-3">
<h5 class="mb-0 fw-semibold"><i class="bi bi-lightning-charge me-2"></i><?= h(tr('إجراءات سريعة', 'Quick Actions')) ?></h5>
<div class="card shadow-sm border-0 h-100" style="border-radius: 12px;">
<div class="card-header bg-white py-3 border-0">
<h5 class="mb-0 fw-semibold"><i class="bi bi-lightning-charge me-2 text-warning"></i><?= h(tr('إجراءات سريعة', 'Quick Actions')) ?></h5>
</div>
<div class="card-body">
<div class="d-grid gap-3">
<a href="<?= h(url_for('pos.php')) ?>" class="btn btn-primary btn-lg d-flex align-items-center justify-content-between">
<a href="<?= h(url_for('pos.php')) ?>" class="btn btn-primary btn-lg d-flex align-items-center justify-content-between" style="border-radius: 10px; box-shadow: 0 4px 6px rgba(13, 110, 253, 0.2);">
<span><i class="bi bi-cart-plus me-2"></i> <?= h(tr('نقطة بيع جديدة', 'New POS Sale')) ?></span>
<i class="bi bi-chevron-right"></i>
</a>
<a href="<?= h(url_for('normal_sale.php')) ?>" class="btn btn-outline-primary btn-lg d-flex align-items-center justify-content-between">
<a href="<?= h(url_for('normal_sale.php')) ?>" class="btn btn-outline-primary btn-lg d-flex align-items-center justify-content-between" style="border-radius: 10px;">
<span><i class="bi bi-receipt me-2"></i> <?= h(tr('بيع عادي', 'Normal Sale')) ?></span>
<i class="bi bi-chevron-right"></i>
</a>
<button type="button" class="btn btn-outline-secondary btn-lg d-flex align-items-center justify-content-between" data-bs-toggle="modal" data-bs-target="#quickNoteModal">
<span><i class="bi bi-pencil-square me-2"></i> <?= h(tr('ملاحظة سريعة', 'Quick Note')) ?></span>
<i class="bi bi-chevron-right"></i>
<button type="button" class="btn btn-light btn-lg d-flex align-items-center justify-content-between border" data-bs-toggle="modal" data-bs-target="#quickNoteModal" style="border-radius: 10px;">
<span class="text-dark"><i class="bi bi-pencil-square me-2"></i> <?= h(tr('ملاحظة سريعة', 'Quick Note')) ?></span>
<i class="bi bi-chevron-right text-dark"></i>
</button>
</div>
<div class="mt-4 pt-4 border-top">
<?php if ($user): ?>
<h6 class="text-muted text-uppercase small fw-bold mb-3"><?= h(tr('معلومات الحساب', 'Account Info')) ?></h6>
<div class="d-flex align-items-center mb-2">
<i class="bi bi-person-badge fs-4 me-3 text-secondary"></i>
<div class="d-flex align-items-center mb-2 p-3 bg-light rounded-3 border">
<div class="bg-white p-2 rounded-circle shadow-sm me-3">
<i class="bi bi-person-badge fs-4 text-primary"></i>
</div>
<div>
<p class="mb-0 fw-semibold"><?= h(current_lang() === 'ar' ? $user['name_ar'] : $user['name_en']) ?></p>
<small class="text-muted"><?= h(role_label($user['role'])) ?> - <?= h(branch_label($user['branch_code'])) ?></small>
<p class="mb-0 fw-bold"><?= h(current_lang() === 'ar' ? $user['name_ar'] : $user['name_en']) ?></p>
<small class="text-muted fw-medium"><?= h(role_label($user['role'])) ?> &bull; <?= h(branch_label($user['branch_code'])) ?></small>
</div>
</div>
<?php else: ?>
<div class="text-center">
<p class="text-muted mb-3"><?= h(tr('جرّب المالك أو مدير الفرع أو الكاشير لرؤية اختلاف الصلاحيات.', 'Try owner, branch manager, or cashier to see different permissions.')) ?></p>
<a class="btn btn-dark w-100" href="<?= h(url_for('login.php')) ?>"><?= h(tr('تسجيل الدخول للبدء', 'Sign in to start')) ?></a>
<div class="text-center p-3 bg-light rounded-3 border">
<p class="text-muted mb-3 small"><?= h(tr('جرّب المالك أو مدير الفرع أو الكاشير لرؤية اختلاف الصلاحيات.', 'Try owner, branch manager, or cashier to see different permissions.')) ?></p>
<a class="btn btn-dark w-100 rounded-pill" href="<?= h(url_for('login.php')) ?>"><?= h(tr('تسجيل الدخول للبدء', 'Sign in to start')) ?></a>
</div>
<?php endif; ?>
</div>
@ -172,35 +254,204 @@ require __DIR__ . '/includes/header.php';
</div>
</div>
<!-- Monthly Sales Chart Row -->
<div class="row g-4 mb-4">
<div class="col-12">
<div class="card shadow-sm border-0" style="border-radius: 12px;">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-semibold"><i class="bi bi-graph-up me-2 text-primary"></i><?= h(tr('مبيعات الأشهر', 'Monthly Sales')) ?></h5>
</div>
<div class="card-body">
<?php if(empty($monthlyLabels)): ?>
<div class="text-center text-muted py-5">
<i class="bi bi-graph-up text-secondary fs-1 d-block mb-3"></i>
<p><?= h(tr('لا توجد بيانات كافية لعرض الرسم البياني', 'Not enough data to show chart')) ?></p>
</div>
<?php else: ?>
<div style="position: relative; height:350px; width:100%">
<canvas id="monthlySalesChart"></canvas>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Modal Form Example -->
<div class="modal fade" id="quickNoteModal" tabindex="-1" aria-labelledby="quickNoteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 16px; border: 0; box-shadow: 0 10px 30px rgba(0,0,0,0.1);">
<form onsubmit="handleQuickNote(event)">
<div class="modal-header">
<h5 class="modal-title" id="quickNoteModalLabel"><?= h(tr('إضافة ملاحظة سريعة', 'Add Quick Note')) ?></h5>
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold" id="quickNoteModalLabel"><i class="bi bi-journal-text me-2 text-primary"></i><?= h(tr('إضافة ملاحظة سريعة', 'Add Quick Note')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="noteTitle" class="form-label"><?= h(tr('العنوان', 'Title')) ?></label>
<input type="text" class="form-control" id="noteTitle" required>
<label for="noteTitle" class="form-label fw-medium"><?= h(tr('العنوان', 'Title')) ?></label>
<input type="text" class="form-control form-control-lg bg-light border-0" id="noteTitle" required>
</div>
<div class="mb-3">
<label for="noteContent" class="form-label"><?= h(tr('التفاصيل', 'Details')) ?></label>
<textarea class="form-control" id="noteContent" rows="3" required></textarea>
<label for="noteContent" class="form-label fw-medium"><?= h(tr('التفاصيل', 'Details')) ?></label>
<textarea class="form-control bg-light border-0" id="noteContent" rows="4" required></textarea>
</div>
</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')) ?></button>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-light px-4" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
<button type="submit" class="btn btn-primary px-4 shadow-sm"><?= h(tr('حفظ الملاحظة', 'Save Note')) ?></button>
</div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
<?php if(!empty($branchLabels)): ?>
const branchCtx = document.getElementById('branchSalesChart').getContext('2d');
new Chart(branchCtx, {
type: 'doughnut',
data: {
labels: <?= json_encode($branchLabels) ?>,
datasets: [{
data: <?= json_encode($branchData) ?>,
backgroundColor: [
'rgba(13, 110, 253, 0.8)',
'rgba(25, 135, 84, 0.8)',
'rgba(13, 202, 240, 0.8)',
'rgba(255, 193, 7, 0.8)',
'rgba(220, 53, 69, 0.8)'
],
borderWidth: 2,
borderColor: '#ffffff',
hoverOffset: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '65%',
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, padding: 20 } },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 12,
callbacks: {
label: function(context) {
let value = context.raw;
return ' ' + value.toFixed(3) + ' <?= tr("ر.ع", "OMR") ?>';
}
}
}
}
}
});
<?php endif; ?>
<?php if(!empty($productLabels)): ?>
const productsCtx = document.getElementById('topProductsChart').getContext('2d');
// Create gradient for bars
let gradient = productsCtx.createLinearGradient(0, 0, 0, 300);
gradient.addColorStop(0, 'rgba(13, 110, 253, 0.9)');
gradient.addColorStop(1, 'rgba(13, 110, 253, 0.2)');
new Chart(productsCtx, {
type: 'bar',
data: {
labels: <?= json_encode($productLabels) ?>,
datasets: [{
label: '<?= h(tr("الكمية المباعة", "Qty Sold")) ?>',
data: <?= json_encode($productData) ?>,
backgroundColor: gradient,
borderColor: '#0d6efd',
borderWidth: 1,
borderRadius: 6,
barPercentage: 0.6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 12
}
},
scales: {
y: {
beginAtZero: true,
ticks: { precision: 0 },
grid: { borderDash: [5, 5], color: '#f0f0f0' }
},
x: {
grid: { display: false }
}
}
}
});
<?php endif; ?>
<?php if(!empty($monthlyLabels)): ?>
const monthlyCtx = document.getElementById('monthlySalesChart').getContext('2d');
// Create gradient for line chart area
let gradientMonthly = monthlyCtx.createLinearGradient(0, 0, 0, 350);
gradientMonthly.addColorStop(0, 'rgba(25, 135, 84, 0.4)');
gradientMonthly.addColorStop(1, 'rgba(25, 135, 84, 0.0)');
new Chart(monthlyCtx, {
type: 'line',
data: {
labels: <?= json_encode($monthlyLabels) ?>,
datasets: [{
label: '<?= h(tr("إجمالي المبيعات", "Total Sales")) ?>',
data: <?= json_encode($monthlyData) ?>,
backgroundColor: gradientMonthly,
borderColor: '#198754',
borderWidth: 3,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#198754',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 12,
callbacks: {
label: function(context) {
let value = context.raw;
return ' ' + value.toFixed(3) + ' <?= tr("ر.ع", "OMR") ?>';
}
}
}
},
scales: {
y: {
beginAtZero: true,
grid: { borderDash: [5, 5], color: '#f0f0f0' }
},
x: {
grid: { display: false }
}
}
}
});
<?php endif; ?>
});
function handleQuickNote(e) {
e.preventDefault();
var modalEl = document.getElementById('quickNoteModal');

159
online_orders.php Normal file
View File

@ -0,0 +1,159 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_permission('sales', 'show'); // or create a specific permission
$pageTitle = tr('طلبات المتجر', 'Online Orders');
$activeNav = 'online_orders';
$db = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'update_status') {
$id = (int)$_POST['id'];
$status = $_POST['status']; // pending, accepted, completed, rejected
$stmt = $db->prepare("UPDATE online_orders SET status = ? WHERE id = ?");
$stmt->execute([$status, $id]);
set_flash('success', tr('تم تحديث حالة الطلب', 'Order status updated'));
redirect_to('online_orders.php');
}
}
$stmt = $db->query("SELECT * FROM online_orders ORDER BY created_at DESC");
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
require __DIR__ . '/includes/header.php';
?>
<div class="row align-items-center mb-4">
<div class="col">
<h3 class="h5 mb-0 fw-bold"><i class="bi bi-cart-check me-2"></i><?= h(tr('طلبات المتجر الإلكتروني', 'Online Store Orders')) ?></h3>
</div>
</div>
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-0 table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light text-muted">
<tr>
<th class="ps-4">#</th>
<th><?= h(tr('التاريخ', 'Date')) ?></th>
<th><?= h(tr('العميل', 'Customer')) ?></th>
<th><?= h(tr('الهاتف', 'Telephone')) ?></th>
<th><?= h(tr('العنوان', 'Address')) ?></th>
<th><?= h(tr('المبلغ', 'Amount')) ?></th>
<th><?= h(tr('الحالة', 'Status')) ?></th>
<th class="pe-4 text-end"><?= h(tr('إجراءات', 'Actions')) ?></th>
</tr>
</thead>
<tbody>
<?php if(empty($orders)): ?>
<tr><td colspan="8" class="text-center py-5 text-muted"><?= h(tr('لا توجد طلبات بعد', 'No orders yet')) ?></td></tr>
<?php else: ?>
<?php foreach($orders as $o):
$statusClass = 'bg-secondary';
$statusText = $o['status'];
if ($o['status'] === 'pending') { $statusClass = 'bg-warning text-dark'; $statusText = tr('قيد الانتظار', 'Pending'); }
elseif ($o['status'] === 'accepted') { $statusClass = 'bg-primary'; $statusText = tr('مقبول', 'Accepted'); }
elseif ($o['status'] === 'completed') { $statusClass = 'bg-success'; $statusText = tr('مكتمل', 'Completed'); }
elseif ($o['status'] === 'rejected') { $statusClass = 'bg-danger'; $statusText = tr('مرفوض', 'Rejected'); }
$items = json_decode($o['items_json'], true) ?: [];
?>
<tr>
<td class="ps-4 fw-bold">#<?= h($o['id']) ?></td>
<td><?= h(date('Y-m-d H:i', strtotime($o['created_at']))) ?></td>
<td class="fw-bold"><?= h($o['customer_name']) ?></td>
<td><?= h($o['customer_phone']) ?></td>
<td style="max-width: 200px;" class="text-truncate" title="<?= h($o['customer_address']) ?>"><?= h($o['customer_address']) ?></td>
<td class="fw-bold text-primary"><?= h(currency($o['total_amount'])) ?></td>
<td><span class="badge <?= $statusClass ?> px-2 py-1"><?= h($statusText) ?></span></td>
<td class="pe-4 text-end">
<button class="btn btn-sm btn-light shadow-sm" onclick='viewOrder(<?= json_encode([
"id" => $o["id"],
"name" => $o["customer_name"],
"phone" => $o["customer_phone"],
"address" => $o["customer_address"],
"total" => $o["total_amount"],
"items" => $items
], JSON_UNESCAPED_UNICODE) ?>)'>
<i class="bi bi-eye"></i> <?= h(tr('عرض', 'View')) ?>
</button>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-primary dropdown-toggle shadow-sm" type="button" data-bs-toggle="dropdown">
<?= h(tr('تغيير الحالة', 'Change Status')) ?>
</button>
<ul class="dropdown-menu shadow">
<li><form method="post"><input type="hidden" name="action" value="update_status"><input type="hidden" name="id" value="<?= h($o['id']) ?>"><input type="hidden" name="status" value="pending"><button class="dropdown-item" type="submit"><?= h(tr('قيد الانتظار', 'Pending')) ?></button></form></li>
<li><form method="post"><input type="hidden" name="action" value="update_status"><input type="hidden" name="id" value="<?= h($o['id']) ?>"><input type="hidden" name="status" value="accepted"><button class="dropdown-item" type="submit"><?= h(tr('مقبول', 'Accepted')) ?></button></form></li>
<li><form method="post"><input type="hidden" name="action" value="update_status"><input type="hidden" name="id" value="<?= h($o['id']) ?>"><input type="hidden" name="status" value="completed"><button class="dropdown-item text-success fw-bold" type="submit"><?= h(tr('مكتمل', 'Completed')) ?></button></form></li>
<li><form method="post"><input type="hidden" name="action" value="update_status"><input type="hidden" name="id" value="<?= h($o['id']) ?>"><input type="hidden" name="status" value="rejected"><button class="dropdown-item text-danger fw-bold" type="submit"><?= h(tr('مرفوض', 'Rejected')) ?></button></form></li>
</ul>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Order Modal -->
<div class="modal fade" id="orderModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content rounded-4 border-0 shadow">
<div class="modal-header border-bottom-0 pb-0 pt-4 px-4">
<h5 class="modal-title fw-bold" id="orderModalLabel"><?= h(tr('تفاصيل الطلب', 'Order Details')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="bg-light p-3 rounded-3 mb-4">
<div class="row">
<div class="col-6 mb-2"><small class="text-muted d-block"><?= h(tr('العميل', 'Customer')) ?></small><strong id="vName"></strong></div>
<div class="col-6 mb-2"><small class="text-muted d-block"><?= h(tr('الهاتف', 'Phone')) ?></small><strong id="vPhone"></strong></div>
<div class="col-12"><small class="text-muted d-block"><?= h(tr('العنوان', 'Address')) ?></small><strong id="vAddress"></strong></div>
</div>
</div>
<h6 class="fw-bold border-bottom pb-2 mb-3"><?= h(tr('المنتجات', 'Products')) ?></h6>
<ul class="list-group list-group-flush mb-4" id="vItems">
</ul>
<div class="d-flex justify-content-between align-items-center bg-primary bg-opacity-10 text-primary p-3 rounded-3">
<h5 class="mb-0 fw-bold"><?= h(tr('الإجمالي', 'Total')) ?></h5>
<h4 class="mb-0 fw-bold" id="vTotal"></h4>
</div>
</div>
<div class="modal-footer border-top-0 pt-0 pb-4 px-4">
<button type="button" class="btn btn-secondary rounded-pill px-4" data-bs-dismiss="modal"><?= h(tr('إغلاق', 'Close')) ?></button>
</div>
</div>
</div>
</div>
<script>
const orderModal = new bootstrap.Modal(document.getElementById('orderModal'));
function viewOrder(order) {
document.getElementById('vName').innerText = order.name;
document.getElementById('vPhone').innerText = order.phone;
document.getElementById('vAddress').innerText = order.address;
document.getElementById('vTotal').innerText = Number(order.total).toFixed(2);
let html = '';
order.items.forEach(item => {
html += `<li class="list-group-item px-0 d-flex justify-content-between align-items-center">
<div>
<span class="fw-bold">${item.name}</span>
<small class="text-muted ms-2 px-2 bg-light rounded">${item.price}</small>
</div>
<span class="badge bg-secondary rounded-pill">x${item.qty}</span>
</li>`;
});
document.getElementById('vItems').innerHTML = html;
orderModal.show();
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

240
outlets.php Normal file
View File

@ -0,0 +1,240 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_permission('settings', 'show');
$pageTitle = tr('الفروع (المنافذ)', 'Outlets');
$activeNav = 'outlets';
$pdo = db();
// Ensure branches table exists
$pdo->exec("CREATE TABLE IF NOT EXISTS branches (
id INT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(50) UNIQUE NOT NULL,
name_ar VARCHAR(100) NOT NULL,
name_en VARCHAR(100) NOT NULL,
city_ar VARCHAR(100),
city_en VARCHAR(100),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)");
// Check if branches table is empty, if so, seed it
$stmt = $pdo->query("SELECT COUNT(*) FROM branches");
if ($stmt->fetchColumn() == 0) {
$defaultBranches = [
['code' => 'muscat', 'name_ar' => 'فرع مسقط', 'name_en' => 'Muscat Branch', 'city_ar' => 'مسقط', 'city_en' => 'Muscat'],
['code' => 'sohar', 'name_ar' => 'فرع صحار', 'name_en' => 'Sohar Branch', 'city_ar' => 'صحار', 'city_en' => 'Sohar'],
['code' => 'nizwa', 'name_ar' => 'فرع نزوى', 'name_en' => 'Nizwa Branch', 'city_ar' => 'نزوى', 'city_en' => 'Nizwa'],
];
$insertStmt = $pdo->prepare("INSERT IGNORE INTO branches (code, name_ar, name_en, city_ar, city_en) VALUES (?, ?, ?, ?, ?)");
foreach ($defaultBranches as $b) {
$insertStmt->execute([$b['code'], $b['name_ar'], $b['name_en'], $b['city_ar'], $b['city_en']]);
}
}
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create') {
$stmt = $pdo->prepare('INSERT INTO branches (code, name_ar, name_en, city_ar, city_en) VALUES (?, ?, ?, ?, ?)');
try {
$stmt->execute([$_POST['code'], $_POST['name_ar'], $_POST['name_en'], $_POST['city_ar'] ?? '', $_POST['city_en'] ?? '']);
set_flash('success', tr('تمت إضافة الفرع بنجاح', 'Outlet added successfully'));
} catch (PDOException $e) {
if ($e->getCode() == 23000) {
set_flash('danger', tr('رمز الفرع موجود مسبقاً', 'Outlet code already exists'));
} else {
set_flash('danger', tr('حدث خطأ', 'An error occurred'));
}
}
redirect_to('outlets.php');
} elseif ($action === 'edit') {
$stmt = $pdo->prepare('UPDATE branches SET name_ar = ?, name_en = ?, city_ar = ?, city_en = ? WHERE code = ?');
$stmt->execute([$_POST['name_ar'], $_POST['name_en'], $_POST['city_ar'] ?? '', $_POST['city_en'] ?? '', $_POST['code']]);
set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully'));
redirect_to('outlets.php');
} elseif ($action === 'delete') {
$stmt = $pdo->prepare('DELETE FROM branches WHERE code = ?');
$stmt->execute([$_POST['code']]);
set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully'));
redirect_to('outlets.php');
}
}
$branchesList = $pdo->query("SELECT * FROM branches ORDER BY id ASC")->fetchAll();
require __DIR__ . '/includes/header.php';
?>
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-bottom-0 pt-4 pb-0 d-flex justify-content-between align-items-center">
<h5 class="fw-bold text-primary mb-0"><i class="bi bi-shop me-2"></i> <?= h($pageTitle) ?></h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة فرع', 'Add Outlet')) ?>
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th><?= h(tr('الرمز', 'Code')) ?></th>
<th><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></th>
<th><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></th>
<th><?= h(tr('المدينة', 'City')) ?></th>
<th class="text-end"><?= h(tr('إجراءات', 'Actions')) ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($branchesList as $b): ?>
<tr>
<td><span class="badge bg-secondary"><?= h($b['code']) ?></span></td>
<td><?= h($b['name_ar']) ?></td>
<td><?= h($b['name_en']) ?></td>
<td><?= h(current_lang() === 'ar' ? $b['city_ar'] : $b['city_en']) ?></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary"
data-bs-toggle="modal"
data-bs-target="#editModal<?= $b['id'] ?>">
<i class="bi bi-pencil"></i>
</button>
<?php if ($b['code'] !== 'muscat'): ?>
<button class="btn btn-sm btn-outline-danger"
data-bs-toggle="modal"
data-bs-target="#deleteModal<?= $b['id'] ?>">
<i class="bi bi-trash"></i>
</button>
<?php endif; ?>
</td>
</tr>
<!-- Edit Modal -->
<div class="modal fade" id="editModal<?= $b['id'] ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<form method="post">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold"><?= h(tr('تعديل فرع', 'Edit Outlet')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-start">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="code" value="<?= h($b['code']) ?>">
<div class="mb-3">
<label class="form-label"><?= h(tr('الرمز', 'Code')) ?></label>
<input type="text" class="form-control bg-light" value="<?= h($b['code']) ?>" disabled>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></label>
<input type="text" name="name_ar" class="form-control" value="<?= h($b['name_ar']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></label>
<input type="text" name="name_en" class="form-control" value="<?= h($b['name_en']) ?>" required>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label"><?= h(tr('المدينة (عربي)', 'City (AR)')) ?></label>
<input type="text" name="city_ar" class="form-control" value="<?= h($b['city_ar']) ?>">
</div>
<div class="col-6 mb-3">
<label class="form-label"><?= h(tr('المدينة (إنجليزي)', 'City (EN)')) ?></label>
<input type="text" name="city_en" class="form-control" value="<?= h($b['city_en']) ?>">
</div>
</div>
</div>
<div class="modal-footer bg-light">
<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')) ?></button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal<?= $b['id'] ?>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<form method="post">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title fw-bold"><?= h(tr('تأكيد الحذف', 'Confirm Deletion')) ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4 text-center">
<i class="bi bi-exclamation-circle text-danger" style="font-size: 3rem;"></i>
<p class="mt-3 fs-5"><?= h(tr('هل أنت متأكد من حذف هذا الفرع؟', 'Are you sure you want to delete this outlet?')) ?></p>
<p class="text-muted fw-bold"><?= h($b['name_ar'] . ' / ' . $b['name_en']) ?></p>
<input type="hidden" name="action" value="delete">
<input type="hidden" name="code" value="<?= h($b['code']) ?>">
</div>
<div class="modal-footer bg-light justify-content-center">
<button type="button" class="btn btn-secondary px-4" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
<button type="submit" class="btn btn-danger px-4"><?= h(tr('حذف', 'Delete')) ?></button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
<?php if (empty($branchesList)): ?>
<tr>
<td colspan="5" class="text-center py-4 text-muted">
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
<?= h(tr('لا توجد فروع', 'No outlets found')) ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Create Modal -->
<div class="modal fade" id="createModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<form method="post">
<div class="modal-header bg-light">
<h5 class="modal-title fw-bold"><?= h(tr('إضافة فرع جديد', 'Add New Outlet')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-start">
<input type="hidden" name="action" value="create">
<div class="mb-3">
<label class="form-label"><?= h(tr('الرمز (إنجليزي فقط بدون مسافات)', 'Code (English only, no spaces)')) ?></label>
<input type="text" name="code" class="form-control" placeholder="e.g. dubai" required pattern="[a-zA-Z0-9_-]+">
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></label>
<input type="text" name="name_ar" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?></label>
<input type="text" name="name_en" class="form-control" required>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label"><?= h(tr('المدينة (عربي)', 'City (AR)')) ?></label>
<input type="text" name="city_ar" class="form-control">
</div>
<div class="col-6 mb-3">
<label class="form-label"><?= h(tr('المدينة (إنجليزي)', 'City (EN)')) ?></label>
<input type="text" name="city_en" class="form-control">
</div>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= h(tr('إلغاء', 'Cancel')) ?></button>
<button type="submit" class="btn btn-primary"><?= h(tr('إضافة', 'Add')) ?></button>
</div>
</form>
</div>
</div>
</div>
<?php require __DIR__ . '/includes/footer.php'; ?>

44
patch_stock.py Normal file
View File

@ -0,0 +1,44 @@
import re
with open('stock.php', 'r') as f:
content = f.read()
# Update openItemModal definition
content = content.replace(
"function openItemModal(sku = '', name = '', price = '', cost_price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '') {",
"function openItemModal(sku = '', name = '', price = '', cost_price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '', in_catalog = 0) {"
)
# Add UI toggle in the form
switch_html = """
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الظهور في المتجر (الكتالوج)', 'Visible in Catalog')) ?></label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" id="item_in_catalog" style="transform: scale(1.3); margin-left: -1.5em; margin-right: 0.5em;">
<label class="form-check-label" for="item_in_catalog" style="margin-right: 2.5em;"><?= h(tr('عرض أونلاين', 'Show Online')) ?></label>
</div>
</div>
"""
content = content.replace(
'<div class="col-md-6 mb-3">\n <label class="form-label"><?= h(tr(\'التكلفة\', \'Cost Price\')) ?></label>',
switch_html + '<div class="col-md-6 mb-3">\n <label class="form-label"><?= h(tr(\'التكلفة\', \'Cost Price\')) ?></label>'
)
# Update Javascript form handling
content = content.replace(
"document.getElementById('item_unit').value = unit_id;",
"document.getElementById('item_unit').value = unit_id;\n document.getElementById('item_in_catalog').checked = (parseInt(in_catalog) === 1);"
)
content = content.replace(
"formData.append('unit_id', document.getElementById('item_unit').value);",
"formData.append('unit_id', document.getElementById('item_unit').value);\n formData.append('in_catalog', document.getElementById('item_in_catalog').checked ? '1' : '0');"
)
# Replace the onclick in the button where we pass the arguments
search_str = "onclick=\"openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['cost_price'] ?? 0) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>')\""
replace_str = "onclick=\"openItemModal('<?= h($row['sku']) ?>', '<?= h(addslashes($row['name'])) ?>', '<?= h($row['price']) ?>', '<?= h($row['cost_price'] ?? 0) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>', '<?= h($row['in_catalog'] ?? 0) ?>')\""
content = content.replace(search_str, replace_str)
with open('stock.php', 'w') as f:
f.write(content)

View File

@ -87,7 +87,7 @@ require __DIR__ . '/includes/header.php';
<td><?= h($row['supplier_name'] ?: '-') ?></td>
<td><?= h($row['reference_no']) ?></td>
<td><?= h(branch_label($row['branch_code'])) ?></td>
<td class="text-muted"><?= h(currency((float)$row['subtotal'])) ?></td>
<td class="text-muted"><?= h(currency((float)$row['total_amount'] - (float)($row['vat_amount'] ?? 0))) ?></td>
<td class="text-muted text-danger"><?= h(currency((float)$row['vat_amount'])) ?></td>
<td class="fw-bold text-success"><?= h(currency((float)$row['total_amount'])) ?></td>
<td>

View File

@ -153,9 +153,13 @@ require __DIR__ . '/includes/header.php';
$vatSum = 0;
$totalSum = 0;
foreach($salesReport as $sale):
$subtotalSum += (float) ($sale['subtotal'] ?? 0);
$vatSum += (float) ($sale['vat_amount'] ?? 0);
$totalSum += (float) $sale['total_amount'];
$vat = (float) ($sale['vat_amount'] ?? 0);
$total = (float) $sale['total_amount'];
$calcSubtotal = $total - $vat;
$subtotalSum += $calcSubtotal;
$vatSum += $vat;
$totalSum += $total;
?>
<tr>
<td><?= h(date('Y-m-d H:i', strtotime((string)$sale['sale_date']))) ?></td>
@ -170,9 +174,9 @@ require __DIR__ . '/includes/header.php';
<span class="badge bg-success"><i class="bi bi-check-circle"></i> <?= h(tr('مدفوع', 'Paid')) ?></span>
<?php endif; ?>
</td>
<td class="text-end"><?= h(currency((float)($sale['subtotal'] ?? 0))) ?></td>
<td class="text-end"><?= h(currency((float)($sale['vat_amount'] ?? 0))) ?></td>
<td class="text-end fw-bold"><?= h(currency((float)$sale['total_amount'])) ?></td>
<td class="text-end"><?= h(currency($calcSubtotal)) ?></td>
<td class="text-end"><?= h(currency($vat)) ?></td>
<td class="text-end fw-bold"><?= h(currency($total)) ?></td>
</tr>
<?php endforeach; ?>
</tbody>

View File

@ -150,7 +150,7 @@ require __DIR__ . '/includes/header.php';
<td><span class="badge text-bg-light border"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></span></td>
<td><?= h((string) $sale['cashier_name']) ?></td>
<td><?= h((string) ($sale['customer_name'] ?: '-')) ?></td>
<td class="text-muted"><?= h(currency((float) $sale['subtotal'])) ?></td>
<td class="text-muted"><?= h(currency((float) $sale['total_amount'] - (float) ($sale['vat_amount'] ?? 0))) ?></td>
<td class="text-muted text-danger"><?= h(currency((float) $sale['vat_amount'])) ?></td>
<td class="fw-bold text-success"><?= h(currency((float) $sale['total_amount'])) ?></td>
<td>

413
shop.php Normal file
View File

@ -0,0 +1,413 @@
<?php
require_once __DIR__ . '/includes/app.php';
$forcePublic = true;
$pageTitle = tr('الطلب عبر الإنترنت', 'Online Ordering');
require __DIR__ . '/includes/header.php';
$db = db();
$stmt = $db->query("
SELECT i.*, c.name_ar as cat_ar, c.name_en as cat_en
FROM items i
LEFT JOIN categories c ON i.category_id = c.id
WHERE i.in_catalog = 1
ORDER BY c.id, i.name
");
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
$catalog = [];
foreach ($items as $item) {
$catName = current_lang() === 'ar' ? ($item['cat_ar'] ?? 'عام') : ($item['cat_en'] ?? 'General');
$catalog[$catName][] = $item;
}
?>
<style>
.shop-item-card {
transition: transform 0.2s, box-shadow 0.2s;
border-radius: 12px;
border: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
height: 100%;
}
.shop-item-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0,0,0,0.1);
}
.shop-item-img {
height: 200px;
object-fit: cover;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
.cart-floating-btn {
position: fixed;
bottom: 30px;
right: 30px;
border-radius: 50%;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
box-shadow: 0 4px 15px rgba(13,110,253,0.4);
z-index: 1000;
}
.cart-badge {
position: absolute;
top: 0;
right: 0;
transform: translate(30%, -30%);
}
body { background-color: #f8f9fa; }
/* Hide scrollbar for category filter */
.category-filter-container::-webkit-scrollbar {
display: none;
}
.category-filter-container {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
<div class="container py-5">
<div class="d-flex justify-content-between align-items-center mb-5 bg-white p-4 rounded-4 shadow-sm">
<div>
<h1 class="fw-bold text-primary mb-1"><i class="bi bi-shop me-2"></i><?= h(get_setting('company_name_' . current_lang(), app_name())) ?></h1>
<p class="text-muted mb-0"><?= h(tr('اطلب الآن وسنقوم بتجهيز طلبك', 'Order now and we will prepare your request')) ?></p>
</div>
<div class="language-switcher">
<a class="btn btn-sm <?= current_lang() === 'ar' ? 'btn-primary' : 'btn-light text-dark' ?> rounded-pill px-3" href="shop.php?lang=ar">AR</a>
<a class="btn btn-sm <?= current_lang() === 'en' ? 'btn-primary' : 'btn-light text-dark' ?> rounded-pill px-3" href="shop.php?lang=en">EN</a>
</div>
</div>
<?php if (!empty($catalog)): ?>
<!-- Search and Filter -->
<div class="row mb-5">
<div class="col-md-6 mb-3 mb-md-0">
<div class="input-group input-group-lg shadow-sm rounded-pill overflow-hidden">
<span class="input-group-text bg-white border-0 ps-4"><i class="bi bi-search text-muted"></i></span>
<input type="text" id="searchInput" class="form-control border-0 px-3" placeholder="<?= h(tr('ابحث عن منتج...', 'Search for a product...')) ?>" onkeyup="filterProducts()">
</div>
</div>
<div class="col-md-6">
<div class="d-flex gap-2 overflow-auto py-2 px-1 category-filter-container" style="white-space: nowrap;" id="categoryFilters">
<button class="btn btn-primary rounded-pill px-4 active" data-filter="all" onclick="setCategoryFilter('all', this)"><?= h(tr('الكل', 'All')) ?></button>
<?php foreach (array_keys($catalog) as $catName): ?>
<button class="btn btn-light rounded-pill px-4 text-dark shadow-sm border" data-filter="<?= htmlspecialchars($catName) ?>" onclick="setCategoryFilter(this.getAttribute('data-filter'), this)"><?= h($catName) ?></button>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if (empty($catalog)): ?>
<div class="text-center py-5">
<i class="bi bi-box-seam display-1 text-muted opacity-50 mb-3 d-block"></i>
<h3 class="text-muted"><?= h(tr('لا توجد منتجات متاحة حالياً', 'No products available currently')) ?></h3>
</div>
<?php else: ?>
<?php foreach ($catalog as $category => $catItems): ?>
<h3 class="fw-bold mb-4 mt-5 border-bottom pb-2 category-title" data-category="<?= htmlspecialchars($category) ?>"><?= h($category) ?></h3>
<div class="row g-4 category-row" data-category="<?= htmlspecialchars($category) ?>">
<?php foreach ($catItems as $item): ?>
<div class="col-sm-6 col-md-4 col-lg-3 product-item" data-name="<?= htmlspecialchars(strtolower($item['name'])) ?>">
<div class="card shop-item-card">
<?php if (!empty($item['image_url'])): ?>
<img src="<?= h($item['image_url']) ?>" class="card-img-top shop-item-img" alt="<?= h($item['name']) ?>">
<?php else: ?>
<div class="bg-light d-flex align-items-center justify-content-center shop-item-img text-muted">
<i class="bi bi-image" style="font-size: 3rem;"></i>
</div>
<?php endif; ?>
<div class="card-body d-flex flex-column">
<h5 class="card-title fw-bold mb-1"><?= h($item['name']) ?></h5>
<p class="text-primary fw-bold fs-5 mb-3"><?= h(currency($item['price'])) ?></p>
<button class="btn btn-outline-primary mt-auto rounded-pill fw-bold" onclick="addToCart(<?= htmlspecialchars(json_encode([
'id' => $item['id'],
'sku' => $item['sku'],
'name' => $item['name'],
'price' => $item['price']
]), ENT_QUOTES, 'UTF-8') ?>)">
<i class="bi bi-cart-plus me-1"></i> <?= h(tr('إضافة للسلة', 'Add to Cart')) ?>
</button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<!-- No results message -->
<div id="noResultsMsg" class="text-center py-5" style="display: none;">
<i class="bi bi-search display-1 text-muted opacity-50 mb-3 d-block"></i>
<h3 class="text-muted"><?= h(tr('لم يتم العثور على نتائج', 'No results found')) ?></h3>
</div>
<?php endif; ?>
</div>
<button class="btn btn-primary cart-floating-btn position-relative" onclick="openCart()">
<i class="bi bi-cart3"></i>
<span class="position-absolute badge rounded-pill bg-danger cart-badge border border-light" id="cartCount">
0
</span>
</button>
<!-- Cart Modal -->
<div class="modal fade" id="cartModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header border-bottom-0 pb-0 pt-4 px-4">
<h5 class="modal-title fw-bold"><i class="bi bi-cart-check me-2 text-primary"></i><?= h(tr('سلة المشتريات', 'Shopping Cart')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body px-4 py-4">
<div id="cartItemsList" class="mb-4">
<!-- Items will be rendered here -->
</div>
<div class="d-flex justify-content-between align-items-center bg-light p-3 rounded-3 mb-4">
<h5 class="mb-0 fw-bold"><?= h(tr('المجموع الإجمالي', 'Total Amount')) ?></h5>
<h4 class="mb-0 fw-bold text-primary" id="cartTotal">0.00</h4>
</div>
<h5 class="fw-bold mb-3 border-bottom pb-2"><?= h(tr('بيانات العميل', 'Customer Details')) ?></h5>
<form id="checkoutForm">
<div class="mb-3">
<label class="form-label fw-semibold"><?= h(tr('الاسم', 'Name')) ?> *</label>
<input type="text" class="form-control form-control-lg rounded-3" id="customerName" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold"><?= h(tr('رقم الهاتف', 'Telephone')) ?> *</label>
<input type="tel" class="form-control form-control-lg rounded-3" id="customerPhone" required>
</div>
<div class="mb-3">
<label class="form-label fw-semibold"><?= h(tr('العنوان', 'Address')) ?> *</label>
<textarea class="form-control form-control-lg rounded-3" id="customerAddress" rows="2" required></textarea>
</div>
</form>
</div>
<div class="modal-footer border-top-0 pt-0 pb-4 px-4 d-flex justify-content-between">
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal"><?= h(tr('إكمال التسوق', 'Continue Shopping')) ?></button>
<button type="button" class="btn btn-success rounded-pill px-5 fw-bold shadow-sm" id="submitOrderBtn" onclick="submitOrder()">
<i class="bi bi-check2-circle me-1"></i> <?= h(tr('تأكيد الطلب', 'Confirm Order')) ?>
</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
let cart = JSON.parse(localStorage.getItem('shop_cart')) || {};
let cartModalInstance = null;
function saveCart() {
localStorage.setItem('shop_cart', JSON.stringify(cart));
updateCartBadge();
}
function updateCartBadge() {
let count = 0;
for (let id in cart) {
count += cart[id].qty;
}
document.getElementById('cartCount').innerText = count;
}
function addToCart(item) {
if (cart[item.id]) {
cart[item.id].qty += 1;
} else {
cart[item.id] = { ...item, qty: 1 };
}
saveCart();
Swal.fire({
title: '<?= h(tr('تمت الإضافة', 'Added')) ?>',
text: item.name,
icon: 'success',
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 1500
});
}
function changeQty(id, delta) {
if (cart[id]) {
cart[id].qty += delta;
if (cart[id].qty <= 0) {
delete cart[id];
}
saveCart();
renderCart();
}
}
function renderCart() {
const list = document.getElementById('cartItemsList');
let html = '';
let total = 0;
if (Object.keys(cart).length === 0) {
html = '<div class="text-center text-muted py-4"><?= h(tr('السلة فارغة', 'Cart is empty')) ?></div>';
} else {
html = '<div class="list-group list-group-flush">';
for (let id in cart) {
const item = cart[id];
const subtotal = item.price * item.qty;
total += subtotal;
html += `
<div class="list-group-item d-flex justify-content-between align-items-center py-3 px-0 border-bottom-dashed">
<div>
<h6 class="mb-1 fw-bold">${item.name}</h6>
<small class="text-muted">${Number(item.price).toFixed(2)}</small>
</div>
<div class="d-flex align-items-center">
<button class="btn btn-sm btn-outline-secondary rounded-circle px-2 me-2" onclick="changeQty(${id}, -1)"><i class="bi bi-dash"></i></button>
<span class="fw-bold px-2">${item.qty}</span>
<button class="btn btn-sm btn-outline-secondary rounded-circle px-2 ms-2 me-4" onclick="changeQty(${id}, 1)"><i class="bi bi-plus"></i></button>
<span class="fw-bold text-primary" style="width: 70px; text-align:right;">${subtotal.toFixed(2)}</span>
</div>
</div>`;
}
html += '</div>';
}
list.innerHTML = html;
document.getElementById('cartTotal').innerText = total.toFixed(2);
}
function openCart() {
if (!cartModalInstance && typeof bootstrap !== 'undefined') {
cartModalInstance = new bootstrap.Modal(document.getElementById('cartModal'));
}
if (cartModalInstance) {
renderCart();
cartModalInstance.show();
} else {
console.error("Bootstrap is not loaded yet.");
}
}
async function submitOrder() {
if (Object.keys(cart).length === 0) {
Swal.fire('<?= h(tr('تنبيه', 'Warning')) ?>', '<?= h(tr('السلة فارغة', 'Cart is empty')) ?>', 'warning');
return;
}
const form = document.getElementById('checkoutForm');
if (!form.reportValidity()) return;
const btn = document.getElementById('submitOrderBtn');
const origText = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
btn.disabled = true;
const data = {
name: document.getElementById('customerName').value,
phone: document.getElementById('customerPhone').value,
address: document.getElementById('customerAddress').value,
items: cart
};
try {
const res = await fetch('api/place_order.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const json = await res.json();
if (json.success) {
cart = {};
saveCart();
if (cartModalInstance) cartModalInstance.hide();
Swal.fire({
title: '<?= h(tr('تم إرسال الطلب بنجاح!', 'Order submitted successfully!')) ?>',
text: '<?= h(tr('سنتواصل معك قريباً لتأكيد الطلب.', 'We will contact you shortly to confirm the order.')) ?>',
icon: 'success',
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>'
});
form.reset();
} else {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', json.error || '<?= h(tr('فشل إرسال الطلب', 'Failed to submit order')) ?>', 'error');
}
} catch (e) {
Swal.fire('<?= h(tr('خطأ', 'Error')) ?>', '<?= h(tr('حدث خطأ في الاتصال', 'Network error')) ?>', 'error');
} finally {
btn.innerHTML = origText;
btn.disabled = false;
}
}
// Filtering Logic
let currentCategory = 'all';
function setCategoryFilter(cat, btn) {
currentCategory = cat;
// Update active button state
document.querySelectorAll('#categoryFilters button').forEach(b => {
b.classList.remove('btn-primary', 'active');
b.classList.add('btn-light', 'text-dark');
});
btn.classList.remove('btn-light', 'text-dark');
btn.classList.add('btn-primary', 'active');
filterProducts();
}
function filterProducts() {
const searchInput = document.getElementById('searchInput');
if (!searchInput) return;
const searchVal = searchInput.value.toLowerCase();
let totalVisible = 0;
document.querySelectorAll('.category-row').forEach(row => {
const catName = row.getAttribute('data-category');
let hasVisibleItems = false;
row.querySelectorAll('.product-item').forEach(item => {
const itemName = item.getAttribute('data-name');
const matchesSearch = itemName.includes(searchVal);
const matchesCategory = currentCategory === 'all' || currentCategory === catName;
if (matchesSearch && matchesCategory) {
item.style.display = '';
hasVisibleItems = true;
totalVisible++;
} else {
item.style.display = 'none';
}
});
// Hide category title if no items visible
const title = row.previousElementSibling;
if (title && title.classList.contains('category-title')) {
if (hasVisibleItems) {
title.style.display = '';
row.style.display = '';
} else {
title.style.display = 'none';
row.style.display = 'none';
}
}
});
const noResultsMsg = document.getElementById('noResultsMsg');
if (noResultsMsg) {
noResultsMsg.style.display = totalVisible === 0 ? 'block' : 'none';
}
}
// init
updateCartBadge();
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

View File

@ -127,6 +127,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$price = (float)($_POST['price'] ?? 0);
$cost_price = (float)($_POST['cost_price'] ?? 0);
$base_stock = (int)($_POST['base_stock'] ?? 0);
$in_catalog = isset($_POST['in_catalog']) && $_POST['in_catalog'] === '1' ? 1 : 0;
$vat = (float)($_POST['vat'] ?? get_setting('vat_percentage', 5));
$category_id = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null;
$supplier_id = !empty($_POST['supplier_id']) ? (int)$_POST['supplier_id'] : null;
@ -161,8 +162,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
exit;
}
$sql = "UPDATE items SET sku=?, name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
$params = [$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id];
$sql = "UPDATE items SET sku=?, name=?, price=?, cost_price=?, base_stock=?, vat=?, category_id=?, supplier_id=?, unit_id=?, in_catalog=? " . ($image_url ? ", image_url=?" : "") . " WHERE sku=?";
$params = [$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $in_catalog];
if ($image_url) {
$params[] = $image_url;
}
@ -174,8 +175,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
echo json_encode(['success' => false, 'error' => 'SKU already exists']);
exit;
}
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $image_url]);
$stmt = $pdo->prepare("INSERT INTO items (sku, name, price, cost_price, base_stock, vat, category_id, supplier_id, unit_id, in_catalog, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$sku, $name, $price, $cost_price, $base_stock, $vat, $category_id, $supplier_id, $unit_id, $in_catalog, $image_url]);
}
echo json_encode(['success' => true]);
@ -376,7 +377,7 @@ require __DIR__ . '/includes/header.php';
<?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['cost_price'] ?? 0) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
<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['cost_price'] ?? 0) ?>', '<?= h($row['base_stock']) ?>', '<?= h($row['vat'] ?? get_setting('vat_percentage', 5)) ?>', '<?= h($row['category_id'] ?? '') ?>', '<?= h($row['supplier_id'] ?? '') ?>', '<?= h($row['unit_id'] ?? '') ?>', '<?= h($row['image_url'] ?? '') ?>', '<?= h($row['in_catalog'] ?? 0) ?>')" data-bs-toggle="tooltip" title="<?= h(tr('تعديل', 'Edit')) ?>">
<i class="bi bi-pencil"></i>
</button>
<button type="button" onclick="printSingleLabel('<?= h(addslashes($row['sku'])) ?>')" class="btn btn-sm btn-outline-secondary rounded-circle shadow-sm ms-1" style="width: 34px; height: 34px; padding: 0;" data-bs-toggle="tooltip" title="<?= h(tr('طباعة ملصق', 'Print Label')) ?>">
@ -477,7 +478,15 @@ require __DIR__ . '/includes/header.php';
<input type="number" step="0.001" class="form-control" id="item_price" required>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الظهور في المتجر (الكتالوج)', 'Visible in Catalog')) ?></label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" id="item_in_catalog" style="transform: scale(1.3); margin-left: -1.5em; margin-right: 0.5em;">
<label class="form-check-label" for="item_in_catalog" style="margin-right: 2.5em;"><?= h(tr('عرض أونلاين', 'Show Online')) ?></label>
</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('التكلفة', 'Cost Price')) ?></label>
<input type="number" step="0.001" class="form-control" id="item_cost_price" required>
</div>
@ -597,7 +606,7 @@ function openImportModal() {
m.show();
}
function openItemModal(sku = '', name = '', price = '', cost_price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '') {
function openItemModal(sku = '', name = '', price = '', cost_price = '', base_stock = '', vat = '<?= h(get_setting('vat_percentage', 5)) ?>', category_id = '', supplier_id = '', unit_id = '', image_url = '', in_catalog = 0) {
document.getElementById('item_original_sku').value = sku;
document.getElementById('item_existing_image_url').value = image_url;
document.getElementById('item_sku').value = sku;
@ -609,6 +618,7 @@ function openItemModal(sku = '', name = '', price = '', cost_price = '', base_st
document.getElementById('item_category').value = category_id;
document.getElementById('item_supplier').value = supplier_id;
document.getElementById('item_unit').value = unit_id;
document.getElementById('item_in_catalog').checked = (parseInt(in_catalog) === 1);
// Remove old image preview if any
const oldPreview = document.getElementById('image_preview');
@ -646,6 +656,7 @@ async function handleItemSubmit(e) {
formData.append('category_id', document.getElementById('item_category').value);
formData.append('supplier_id', document.getElementById('item_supplier').value);
formData.append('unit_id', document.getElementById('item_unit').value);
formData.append('in_catalog', document.getElementById('item_in_catalog').checked ? '1' : '0');
formData.append('existing_image_url', document.getElementById('item_existing_image_url').value);
const picInput = document.getElementById('item_picture');