Autosave: 20260419-023017

This commit is contained in:
Flatlogic Bot 2026-04-19 02:30:10 +00:00
parent 5e9301e720
commit ccaa56bcff
21 changed files with 3082 additions and 563 deletions

View File

@ -1,403 +1,139 @@
:root {
--sidebar-width: 250px;
}
body {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
color: #212529;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
margin: 0;
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f6f9;
}
.main-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
#wrapper {
overflow-x: hidden;
}
#sidebar-wrapper {
min-height: 100vh;
margin-left: calc(-1 * var(--sidebar-width));
transition: margin .25s ease-out;
width: var(--sidebar-width);
position: fixed;
top: 0;
left: 0;
z-index: 1000;
background: #343a40; /* Dark theme sidebar */
}
[dir="rtl"] #sidebar-wrapper {
margin-left: 0;
margin-right: calc(-1 * var(--sidebar-width));
left: auto;
right: 0;
}
#wrapper.toggled #sidebar-wrapper {
margin-left: 0;
}
[dir="rtl"] #wrapper.toggled #sidebar-wrapper {
margin-right: 0;
}
#page-content-wrapper {
min-width: 100vw;
transition: margin .25s ease-out;
}
@media (min-width: 768px) {
#sidebar-wrapper {
margin-left: 0;
}
[dir="rtl"] #sidebar-wrapper {
margin-right: 0;
}
#page-content-wrapper {
min-width: 0;
width: 100%;
padding: 20px;
box-sizing: border-box;
position: relative;
z-index: 1;
margin-left: var(--sidebar-width);
}
[dir="rtl"] #page-content-wrapper {
margin-left: 0;
margin-right: var(--sidebar-width);
}
#wrapper.toggled #sidebar-wrapper {
margin-left: calc(-1 * var(--sidebar-width));
}
[dir="rtl"] #wrapper.toggled #sidebar-wrapper {
margin-right: calc(-1 * var(--sidebar-width));
}
#wrapper.toggled #page-content-wrapper {
margin-left: 0;
}
[dir="rtl"] #wrapper.toggled #page-content-wrapper {
margin-right: 0;
}
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
.sidebar-heading {
padding: 1rem 1.25rem;
font-size: 1.25rem;
color: #fff;
background: #212529;
}
.list-group-item {
border: none;
padding: 0.85rem 1.25rem;
background-color: transparent;
color: #c2c7d0;
font-weight: 500;
}
.list-group-item:hover, .list-group-item.active {
background-color: rgba(255, 255, 255, 0.1);
color: #fff;
}
.list-group-item i {
margin-right: 10px;
width: 20px;
text-align: center;
}
[dir="rtl"] .list-group-item i {
margin-right: 0;
margin-left: 10px;
}
.chat-container {
width: 100%;
max-width: 600px;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
display: flex;
flex-direction: column;
height: 85vh;
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
overflow: hidden;
.top-navbar {
background-color: #fff;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.chat-header {
padding: 1.5rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
background: rgba(255, 255, 255, 0.5);
font-weight: 700;
font-size: 1.1rem;
display: flex;
justify-content: space-between;
align-items: center;
.card {
box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2);
margin-bottom: 1rem;
border: 0;
border-radius: 0.25rem;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
.message {
max-width: 85%;
padding: 0.85rem 1.1rem;
border-radius: 16px;
line-height: 1.5;
font-size: 0.95rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px) scale(0.95); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.message.visitor {
align-self: flex-end;
background: linear-gradient(135deg, #212529 0%, #343a40 100%);
color: #fff;
border-bottom-right-radius: 4px;
}
.message.bot {
align-self: flex-start;
background: #ffffff;
color: #212529;
border-bottom-left-radius: 4px;
}
.chat-input-area {
padding: 1.25rem;
background: rgba(255, 255, 255, 0.5);
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
.chat-input-area form {
display: flex;
gap: 0.75rem;
}
.chat-input-area input {
flex: 1;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
padding: 0.75rem 1rem;
outline: none;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
}
.chat-input-area input:focus {
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
}
.chat-input-area button {
background: #212529;
color: #fff;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.chat-input-area button:hover {
background: #000;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
/* Background Animations */
.bg-animations {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
.blob {
position: absolute;
width: 500px;
height: 500px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
filter: blur(80px);
animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
}
.blob-1 {
top: -10%;
left: -10%;
background: rgba(238, 119, 82, 0.4);
}
.blob-2 {
bottom: -10%;
right: -10%;
background: rgba(35, 166, 213, 0.4);
animation-delay: -7s;
width: 600px;
height: 600px;
}
.blob-3 {
top: 40%;
left: 30%;
background: rgba(231, 60, 126, 0.3);
animation-delay: -14s;
width: 450px;
height: 450px;
}
@keyframes move {
0% { transform: translate(0, 0) rotate(0deg) scale(1); }
33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
100% { transform: translate(0, 0) rotate(360deg) scale(1); }
}
.header-link {
font-size: 14px;
color: #fff;
text-decoration: none;
background: rgba(0, 0, 0, 0.2);
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.header-link:hover {
background: rgba(0, 0, 0, 0.4);
text-decoration: none;
}
/* Admin Styles */
.admin-container {
max-width: 900px;
margin: 3rem auto;
padding: 2.5rem;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 24px;
box-shadow: 0 20px 50px rgba(0,0,0,0.15);
border: 1px solid rgba(255, 255, 255, 0.4);
position: relative;
z-index: 1;
}
.admin-container h1 {
margin-top: 0;
color: #212529;
font-weight: 800;
}
.table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
margin-top: 1.5rem;
.card-header {
background-color: transparent;
border-bottom: 1px solid rgba(0,0,0,.125);
font-weight: 600;
}
.table th {
background: transparent;
border: none;
padding: 1rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 1px;
font-weight: 600;
color: #495057;
background-color: #f8f9fa;
}
.table td {
background: #fff;
padding: 1rem;
border: none;
/* Modal styles */
.modal-header {
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.modal-footer {
border-top: 1px solid #dee2e6;
background-color: #f8f9fa;
}
.table tr td:first-child { border-radius: 12px 0 0 12px; }
.table tr td:last-child { border-radius: 0 12px 12px 0; }
.form-group {
margin-bottom: 1.25rem;
body.auth-body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #e9ecef;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
}
.form-control {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
background: #fff;
transition: all 0.3s ease;
box-sizing: border-box;
}
.form-control:focus {
outline: none;
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-links {
display: flex;
gap: 1rem;
}
.admin-card {
background: rgba(255, 255, 255, 0.6);
padding: 2rem;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.5);
margin-bottom: 2.5rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
}
.admin-card h3 {
margin-top: 0;
margin-bottom: 1.5rem;
font-weight: 700;
}
.btn-delete {
background: #dc3545;
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 4px;
cursor: pointer;
}
.btn-add {
background: #212529;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-top: 1rem;
}
.btn-save {
background: #0088cc;
color: white;
border: none;
padding: 0.8rem 1.5rem;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
width: 100%;
transition: all 0.3s ease;
}
.webhook-url {
font-size: 0.85em;
color: #555;
margin-top: 0.5rem;
}
.history-table-container {
overflow-x: auto;
background: rgba(255, 255, 255, 0.4);
padding: 1rem;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.history-table {
width: 100%;
}
.history-table-time {
width: 15%;
white-space: nowrap;
font-size: 0.85em;
color: #555;
}
.history-table-user {
width: 35%;
background: rgba(255, 255, 255, 0.3);
border-radius: 8px;
padding: 8px;
}
.history-table-ai {
width: 50%;
background: rgba(255, 255, 255, 0.5);
border-radius: 8px;
padding: 8px;
}
.no-messages {
text-align: center;
color: #777;
/* Sidebar Sub-menu */
[data-bs-toggle="collapse"][aria-expanded="true"] .toggle-icon {
transform: rotate(180deg);
}

View File

@ -1,39 +1,125 @@
// Basic logic for cart interactions
document.addEventListener('DOMContentLoaded', () => {
const chatForm = document.getElementById('chat-form');
const chatInput = document.getElementById('chat-input');
const chatMessages = document.getElementById('chat-messages');
const saleForm = document.querySelector('[data-sale-form]');
if (!saleForm) return;
const appendMessage = (text, sender) => {
const msgDiv = document.createElement('div');
msgDiv.classList.add('message', sender);
msgDiv.textContent = text;
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
};
let cart = [];
const cartJsonInput = document.getElementById('cart_json');
const cartLinesContainer = document.getElementById('cart-lines');
const cartEmptyState = document.getElementById('cart-empty-state');
const cartCountLabel = document.getElementById('cart-count-label');
const cartSubtotalLabel = document.getElementById('cart-subtotal');
const cartTotalLabel = document.getElementById('cart-total');
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const message = chatInput.value.trim();
if (!message) return;
function renderCart() {
if (cart.length === 0) {
cartEmptyState.style.display = 'block';
cartLinesContainer.innerHTML = '';
cartCountLabel.textContent = '0 ' + (window.saleLabels ? window.saleLabels.empty : 'items');
cartSubtotalLabel.textContent = '0.00';
cartTotalLabel.textContent = '0.00';
cartJsonInput.value = '[]';
return;
}
appendMessage(message, 'visitor');
chatInput.value = '';
cartEmptyState.style.display = 'none';
let html = '';
let totalItems = 0;
let subtotal = 0;
try {
const response = await fetch('api/chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message })
});
const data = await response.json();
// Artificial delay for realism
setTimeout(() => {
appendMessage(data.reply, 'bot');
}, 500);
} catch (error) {
console.error('Error:', error);
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
}
cart.forEach((item, index) => {
const lineTotal = item.qty * item.price;
totalItems += item.qty;
subtotal += lineTotal;
html += `
<div class="cart-line mb-2 p-2 border rounded bg-white shadow-sm d-flex justify-content-between align-items-center">
<div>
<div class="fw-semibold small">${item.name}</div>
<div class="text-muted" style="font-size: 0.75rem;">${item.qty} x ${item.price.toFixed(3)}</div>
</div>
<div class="d-flex align-items-center gap-2">
<span class="fw-bold text-primary">${lineTotal.toFixed(3)}</span>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="updateCartQty(${index}, -1)">-</button>
<button type="button" class="btn btn-outline-secondary" onclick="updateCartQty(${index}, 1)">+</button>
<button type="button" class="btn btn-outline-danger" onclick="removeCartItem(${index})"><i class="bi bi-trash"></i></button>
</div>
</div>
</div>
`;
});
});
cartLinesContainer.innerHTML = html;
cartCountLabel.textContent = totalItems + ' items';
cartSubtotalLabel.textContent = subtotal.toFixed(3);
cartTotalLabel.textContent = subtotal.toFixed(3);
cartJsonInput.value = JSON.stringify(cart);
}
window.updateCartQty = function(index, delta) {
if (cart[index]) {
cart[index].qty += delta;
if (cart[index].qty <= 0) {
cart.splice(index, 1);
}
renderCart();
}
};
window.removeCartItem = function(index) {
cart.splice(index, 1);
renderCart();
};
document.querySelectorAll('[data-add-product]').forEach(btn => {
btn.addEventListener('click', () => {
const sku = btn.dataset.sku;
const name = btn.dataset.name;
const price = parseFloat(btn.dataset.price);
const existing = cart.find(i => i.sku === sku);
if (existing) {
existing.qty++;
} else {
cart.push({ sku, name, price, qty: 1 });
}
// SweetAlert2 Toast for adding product
if (typeof Swal !== 'undefined') {
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: name + ' added',
showConfirmButton: false,
timer: 1500
});
}
renderCart();
});
});
const clearBtn = document.querySelector('[data-clear-cart]');
if (clearBtn) {
clearBtn.addEventListener('click', () => {
cart = [];
renderCart();
});
}
// Handle form submission warning if empty
saleForm.addEventListener('submit', (e) => {
if (cart.length === 0) {
e.preventDefault();
if (typeof Swal !== 'undefined') {
Swal.fire('Empty Cart', 'Please add items before saving.', 'warning');
} else {
alert('Cart is empty');
}
}
});
renderCart();
});

226
categories.php Normal file
View File

@ -0,0 +1,226 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$pageTitle = tr('التصنيفات', 'Categories');
$activeNav = 'categories';
$pdo = db();
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create') {
$stmt = $pdo->prepare('INSERT INTO categories (name_ar, name_en, description) VALUES (?, ?, ?)');
$stmt->execute([$_POST['name_ar'], $_POST['name_en'], $_POST['description'] ?? '']);
set_flash('success', tr('تمت إضافة التصنيف بنجاح', 'Category added successfully'));
redirect_to('categories.php');
} elseif ($action === 'edit') {
$stmt = $pdo->prepare('UPDATE categories SET name_ar = ?, name_en = ?, description = ? WHERE id = ?');
$stmt->execute([$_POST['name_ar'], $_POST['name_en'], $_POST['description'] ?? '', $_POST['id']]);
set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully'));
redirect_to('categories.php');
} elseif ($action === 'delete') {
$stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?');
$stmt->execute([$_POST['id']]);
set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully'));
redirect_to('categories.php');
}
}
// Pagination & Search
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
$search = $_GET['q'] ?? '';
$where = '1=1';
$params = [];
if ($search) {
$where .= ' AND (name_ar LIKE ? OR name_en LIKE ?)';
$params[] = "%$search%";
$params[] = "%$search%";
}
$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM categories WHERE $where");
$totalStmt->execute($params);
$total = $totalStmt->fetchColumn();
$totalPages = ceil($total / $limit);
$queryStmt = $pdo->prepare("SELECT * FROM categories WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
$queryStmt->execute($params);
$items = $queryStmt->fetchAll();
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h3 class="h5 mb-2"><i class="bi bi-tags me-2"></i><?= h($pageTitle) ?></h3>
<p class="text-muted mb-0"><?= h(tr('إدارة تصنيفات المنتجات', 'Manage product categories')) ?></p>
</div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة تصنيف', 'Add Category')) ?>
</button>
</div>
<form class="d-flex mb-3" method="GET" action="categories.php">
<div class="input-group" style="max-width: 400px;">
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث...', 'Search...')) ?>" value="<?= h($search) ?>">
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</div>
</form>
</section>
<section class="surface-card">
<div class="table-responsive">
<table class="table app-table align-middle mb-0">
<thead>
<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>
</tr>
</thead>
<tbody>
<?php if(empty($items)): ?>
<tr><td colspan="5" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<?php endif; ?>
<?php foreach ($items as $item): ?>
<tr>
<td><?= h($item['id']) ?></td>
<td><?= h($item['name_ar']) ?></td>
<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')) ?>">
<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')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('categories.php', ['p' => $i, 'q' => $search])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
</section>
<!-- Add Modal -->
<div class="modal fade" id="addModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="categories.php">
<input type="hidden" name="action" value="create">
<div class="modal-header">
<h5 class="modal-title"><?= h(tr('إضافة تصنيف', 'Add Category')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<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="mb-3">
<label class="form-label"><?= h(tr('الوصف', 'Description')) ?></label>
<textarea name="description" class="form-control"></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>
</form>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="categories.php">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="id" id="edit_id">
<div class="modal-header">
<h5 class="modal-title"><?= h(tr('تعديل', 'Edit')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم (عربي)', 'Name (AR)')) ?></label>
<input type="text" name="name_ar" id="edit_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" id="edit_name_en" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('الوصف', 'Description')) ?></label>
<textarea name="description" id="edit_description" class="form-control"></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>
</form>
</div>
</div>
</div>
<!-- Delete Form -->
<form id="deleteForm" method="POST" action="categories.php" style="display:none;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" id="delete_id">
</form>
<script>
function editItem(item) {
document.getElementById('edit_id').value = item.id;
document.getElementById('edit_name_ar').value = item.name_ar;
document.getElementById('edit_name_en').value = item.name_en;
document.getElementById('edit_description').value = item.description || '';
new bootstrap.Modal(document.getElementById('editModal')).show();
}
function deleteItem(id) {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => {
if (result.isConfirmed) {
document.getElementById('delete_id').value = id;
document.getElementById('deleteForm').submit();
}
});
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

242
customers.php Normal file
View File

@ -0,0 +1,242 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$pageTitle = tr('العملاء', 'Customers');
$activeNav = 'customers';
$pdo = db();
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create') {
$stmt = $pdo->prepare('INSERT INTO customers (name, phone, email, address) VALUES (?, ?, ?, ?)');
$stmt->execute([$_POST['name'], $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '']);
set_flash('success', tr('تمت إضافة العميل بنجاح', 'Customer added successfully'));
redirect_to('customers.php');
} elseif ($action === 'edit') {
$stmt = $pdo->prepare('UPDATE customers SET name = ?, phone = ?, email = ?, address = ? WHERE id = ?');
$stmt->execute([$_POST['name'], $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '', $_POST['id']]);
set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully'));
redirect_to('customers.php');
} elseif ($action === 'delete') {
$stmt = $pdo->prepare('DELETE FROM customers WHERE id = ?');
$stmt->execute([$_POST['id']]);
set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully'));
redirect_to('customers.php');
}
}
// Pagination & Search
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
$search = $_GET['q'] ?? '';
$where = '1=1';
$params = [];
if ($search) {
$where .= ' AND (name LIKE ? OR phone LIKE ? OR email LIKE ?)';
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM customers WHERE $where");
$totalStmt->execute($params);
$total = $totalStmt->fetchColumn();
$totalPages = ceil($total / $limit);
$queryStmt = $pdo->prepare("SELECT * FROM customers WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
$queryStmt->execute($params);
$items = $queryStmt->fetchAll();
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h3 class="h5 mb-2"><i class="bi bi-people-fill me-2"></i><?= h($pageTitle) ?></h3>
<p class="text-muted mb-0"><?= h(tr('إدارة حسابات العملاء', 'Manage customer accounts')) ?></p>
</div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة عميل', 'Add Customer')) ?>
</button>
</div>
<form class="d-flex mb-3" method="GET" action="customers.php">
<div class="input-group" style="max-width: 400px;">
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث بالاسم أو الهاتف...', 'Search name or phone...')) ?>" value="<?= h($search) ?>">
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</div>
</form>
</section>
<section class="surface-card">
<div class="table-responsive">
<table class="table app-table align-middle mb-0">
<thead>
<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>
</tr>
</thead>
<tbody>
<?php if(empty($items)): ?>
<tr><td colspan="6" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<?php endif; ?>
<?php foreach ($items as $item): ?>
<tr>
<td><?= h($item['id']) ?></td>
<td class="fw-semibold"><?= h($item['name']) ?></td>
<td><?= h($item['phone']) ?></td>
<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')) ?>">
<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')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('customers.php', ['p' => $i, 'q' => $search])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
</section>
<!-- Add Modal -->
<div class="modal fade" id="addModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="customers.php">
<input type="hidden" name="action" value="create">
<div class="modal-header">
<h5 class="modal-title"><?= h(tr('إضافة عميل', 'Add Customer')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم', 'Name')) ?></label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الهاتف', 'Phone')) ?></label>
<input type="text" name="phone" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('البريد الإلكتروني', 'Email')) ?></label>
<input type="email" name="email" class="form-control">
</div>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('العنوان', 'Address')) ?></label>
<textarea name="address" class="form-control"></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>
</form>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="customers.php">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="id" id="edit_id">
<div class="modal-header">
<h5 class="modal-title"><?= h(tr('تعديل', 'Edit')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم', 'Name')) ?></label>
<input type="text" name="name" id="edit_name" class="form-control" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الهاتف', 'Phone')) ?></label>
<input type="text" name="phone" id="edit_phone" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('البريد الإلكتروني', 'Email')) ?></label>
<input type="email" name="email" id="edit_email" class="form-control">
</div>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('العنوان', 'Address')) ?></label>
<textarea name="address" id="edit_address" class="form-control"></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>
</form>
</div>
</div>
</div>
<!-- Delete Form -->
<form id="deleteForm" method="POST" action="customers.php" style="display:none;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" id="delete_id">
</form>
<script>
function editItem(item) {
document.getElementById('edit_id').value = item.id;
document.getElementById('edit_name').value = item.name;
document.getElementById('edit_phone').value = item.phone || '';
document.getElementById('edit_email').value = item.email || '';
document.getElementById('edit_address').value = item.address || '';
new bootstrap.Modal(document.getElementById('editModal')).show();
}
function deleteItem(id) {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => {
if (result.isConfirmed) {
document.getElementById('delete_id').value = id;
document.getElementById('deleteForm').submit();
}
});
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

8
healthz.php Normal file
View File

@ -0,0 +1,8 @@
<?php
declare(strict_types=1);
http_response_code(200);
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'status' => 'ok',
'time' => date(DATE_ATOM),
], JSON_UNESCAPED_UNICODE);

476
includes/app.php Normal file
View File

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

28
includes/footer.php Normal file
View File

@ -0,0 +1,28 @@
<?php
$user = current_user();
$isPublic = !isset($user) || !$user;
?>
<?php if ($isPublic): ?>
</main>
<?php else: ?>
</div> <!-- /.container-fluid -->
</div> <!-- /#page-content-wrapper -->
</div> <!-- /#wrapper -->
<?php endif; ?>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script>
// Menu Toggle Script
document.addEventListener('DOMContentLoaded', function() {
var toggleBtn = document.getElementById("menu-toggle");
if (toggleBtn) {
toggleBtn.addEventListener('click', function(e) {
e.preventDefault();
document.getElementById("wrapper").classList.toggle("toggled");
});
}
});
</script>
<script src="assets/js/main.js?v=<?= h(date('YmdHi')) ?>"></script>
</body>
</html>

157
includes/header.php Normal file
View File

@ -0,0 +1,157 @@
<?php
require_once __DIR__ . '/app.php';
$user = current_user();
$activeNav = $activeNav ?? 'dashboard';
$pageTitle = $pageTitle ?? app_name();
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$projectName = $_SERVER['PROJECT_NAME'] ?? app_name();
$metaDescription = $projectDescription !== '' ? $projectDescription : tr('منصة مبيعات ومخزون متعددة الفروع لحلوى الريامي.', 'Multi-branch sweets sales and stock workspace for Al Riyami Sweets.');
$flash = pull_flash();
$assetVersion = date('YmdHi');
// Determine if we are on a public page (like login)
$isPublic = !isset($user) || !$user;
?>
<!doctype html>
<html lang="<?= h(current_lang()) ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= h($pageTitle) ?> · <?= h($projectName) ?></title>
<?php if ($projectDescription): ?>
<meta name="description" content='<?= h($projectDescription) ?>' />
<meta property="og:description" content="<?= h($projectDescription) ?>" />
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
<?php else: ?>
<meta name="description" content="<?= h($metaDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
<?php endif; ?>
<meta name="theme-color" content="#343a40" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h($assetVersion) ?>">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body class="<?= $isPublic ? 'auth-body' : '' ?>">
<?php if ($isPublic): ?>
<!-- Public Layout for Login -->
<main class="container">
<?php else: ?>
<!-- Private Admin Layout -->
<div class="d-flex" id="wrapper">
<!-- Sidebar -->
<div class="border-end bg-dark text-white shadow-sm" id="sidebar-wrapper">
<div class="sidebar-heading text-center py-4 fs-5 fw-bold text-uppercase border-bottom border-secondary">
<i class="bi bi-shop me-2"></i><?= h(tr('حلوى الريامي', 'Al Riyami Sweets')) ?>
</div>
<div class="p-3 text-center border-bottom border-secondary">
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $user['name_ar'] : $user['name_en']) ?></div>
<div class="text-white-50 small"><?= h(role_label($user['role'])) ?> · <?= h(branch_label($user['branch_code'])) ?></div>
</div>
<div class="list-group list-group-flush my-3 pb-5">
<a class="list-group-item list-group-item-action <?= $activeNav === 'dashboard' ? 'active' : '' ?>" href="<?= h(url_for('index.php')) ?>">
<i class="bi bi-speedometer2"></i> <?= h(tr('لوحة التحكم', 'Dashboard')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'pos' ? 'active' : '' ?>" href="<?= h(url_for('pos.php')) ?>">
<i class="bi bi-cart-check"></i> <?= h(tr('نقاط البيع', 'POS Sale')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'normal' ? 'active' : '' ?>" href="<?= h(url_for('normal_sale.php')) ?>">
<i class="bi bi-receipt"></i> <?= h(tr('بيع عادي', 'Normal Sale')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'sales' ? 'active' : '' ?>" href="<?= h(url_for('sales.php')) ?>">
<i class="bi bi-journal-text"></i> <?= h(tr('المبيعات', 'Sales')) ?>
</a>
<a class="list-group-item list-group-item-action <?= in_array($activeNav, ['stock', 'categories']) ? '' : 'collapsed' ?>" data-bs-toggle="collapse" href="#collapseStock" role="button" aria-expanded="<?= in_array($activeNav, ['stock', 'categories']) ? 'true' : 'false' ?>" aria-controls="collapseStock">
<div class="d-flex justify-content-between align-items-center w-100">
<span><i class="bi bi-box-seam"></i> <?= h(tr('المخزون', 'Inventory')) ?></span>
<i class="bi bi-chevron-down toggle-icon" style="transition: transform 0.2s;"></i>
</div>
</a>
<div class="collapse <?= in_array($activeNav, ['stock', 'categories']) ? 'show' : '' ?>" id="collapseStock">
<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 <?= $activeNav === 'stock' ? 'active' : '' ?>" href="<?= h(url_for('stock.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
<i class="bi bi-dot"></i> <?= h(tr('قائمة الأصناف', 'Items List')) ?>
</a>
<a class="list-group-item list-group-item-action <?= $activeNav === 'categories' ? 'active' : '' ?>" href="<?= h(url_for('categories.php')) ?>" style="padding-left: 2.5rem; padding-right: 2.5rem;">
<i class="bi bi-dot"></i> <?= h(tr('التصنيفات', 'Categories')) ?>
</a>
</div>
</div>
<a class="list-group-item list-group-item-action <?= $activeNav === 'purchases' ? 'active' : '' ?>" href="<?= h(url_for('purchases.php')) ?>">
<i class="bi bi-bag-plus"></i> <?= h(tr('المشتريات', 'Purchases')) ?>
</a>
<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 === 'customers' ? 'active' : '' ?>" href="<?= h(url_for('customers.php')) ?>">
<i class="bi bi-people-fill"></i> <?= h(tr('العملاء', 'Customers')) ?>
</a>
<?php if ($user && in_array($user['role'], ['owner', 'manager'], true)): ?>
<a class="list-group-item list-group-item-action <?= $activeNav === 'reports' ? 'active' : '' ?>" href="<?= h(url_for('reports.php')) ?>">
<i class="bi bi-bar-chart"></i> <?= h(tr('التقارير', 'Reports')) ?>
</a>
<?php endif; ?>
<?php if ($user && $user['role'] === 'owner'): ?>
<a class="list-group-item list-group-item-action <?= $activeNav === 'users' ? 'active' : '' ?>" href="<?= h(url_for('users.php')) ?>">
<i class="bi bi-people"></i> <?= h(tr('المستخدمون والأدوار', 'Users & Roles')) ?>
</a>
<?php endif; ?>
</div>
</div>
<!-- /#sidebar-wrapper -->
<!-- Page Content -->
<div id="page-content-wrapper">
<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">
<button class="btn btn-outline-secondary me-2" id="menu-toggle"><i class="bi bi-list"></i></button>
<h4 class="mb-0 ms-2 fw-semibold d-none d-md-block"><?= h($pageTitle) ?></h4>
</div>
<div class="d-flex align-items-center gap-3">
<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>
</div>
<div class="dropdown">
<button class="btn btn-light dropdown-toggle border" type="button" id="userMenu" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> <?= h(current_lang() === 'ar' ? $user['name_ar'] : $user['name_en']) ?>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="userMenu">
<li><a class="dropdown-item" href="<?= h(url_for('logout.php')) ?>"><i class="bi bi-box-arrow-right text-danger"></i> <?= h(tr('تسجيل الخروج', 'Sign out')) ?></a></li>
</ul>
</div>
</div>
</div>
</nav>
<div class="container-fluid p-4">
<!-- Flash messages integration using SweetAlert2 -->
<?php if ($flash): ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
Swal.fire({
icon: '<?= h($flash['type']) === "danger" ? "error" : (h($flash['type']) === "warning" ? "warning" : "success") ?>',
title: '<?= h($flash['message']) ?>',
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true
});
});
</script>
<?php endif; ?>
<?php endif; ?>

180
includes/sale_form.php Normal file
View File

@ -0,0 +1,180 @@
<?php
require_once __DIR__ . '/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$pageTitle = $saleMode === 'normal' ? tr('بيع عادي', 'Normal Sale') : tr('نقاط البيع', 'POS Sale');
$activeNav = $saleMode === 'normal' ? 'normal' : 'pos';
$error = '';
$catalog = catalog();
$allowedBranches = $user['role'] === 'owner' ? array_keys(branches()) : [$user['branch_code']];
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', $saleMode === 'normal'
? tr('تم حفظ البيع العادي بنجاح.', 'Normal sale saved successfully.')
: tr('تم حفظ عملية POS بنجاح.', 'POS sale saved successfully.'));
redirect_to('sale.php', ['id' => $saleId]);
}
}
}
require __DIR__ . '/header.php';
?>
<section class="row g-4">
<div class="col-xl-7">
<div class="surface-card h-100">
<div class="d-flex justify-content-between align-items-center mb-3 gap-3 flex-wrap">
<div>
<h3 class="h5 mb-1"><?= h($pageTitle) ?></h3>
<div class="small text-muted"><?= h(tr('أضف المنتجات إلى السلة ثم احفظ العملية.', 'Add products to the cart and save the transaction.')) ?></div>
</div>
<span class="badge text-bg-light border px-3 py-2"><?= h(sale_mode_label($saleMode)) ?></span>
</div>
<?php if ($error !== ''): ?>
<div class="alert alert-warning"><?= h($error) ?></div>
<?php endif; ?>
<form method="post" class="d-grid gap-4" id="sale-form" data-sale-form>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="branch_code"><?= h(tr('الفرع', 'Branch')) ?></label>
<select class="form-select" id="branch_code" name="branch_code" <?= count($allowedBranches) === 1 ? 'aria-readonly="true"' : '' ?>>
<?php foreach ($allowedBranches as $branchCode): ?>
<option value="<?= h($branchCode) ?>"><?= h(branch_label($branchCode)) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="payment_method"><?= h(tr('طريقة الدفع', 'Payment method')) ?></label>
<select class="form-select" id="payment_method" name="payment_method">
<option value="cash"><?= h(tr('نقداً', 'Cash')) ?></option>
<option value="card"><?= h(tr('بطاقة', 'Card')) ?></option>
<option value="transfer"><?= h(tr('تحويل', 'Transfer')) ?></option>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="customer_name"><?= h(tr('اسم العميل', 'Customer name')) ?></label>
<input class="form-control" id="customer_name" name="customer_name" maxlength="120" placeholder="<?= h(tr('اختياري', 'Optional')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" for="notes"><?= h(tr('ملاحظات', 'Notes')) ?></label>
<input class="form-control" id="notes" name="notes" maxlength="500" placeholder="<?= h(tr('طلب خاص أو ملاحظة داخلية', 'Special request or internal note')) ?>">
</div>
</div>
<input type="hidden" name="cart_json" id="cart_json" value="[]">
<div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="h6 mb-0"><?= h(tr('الأصناف السريعة', 'Quick products')) ?></h4>
<span class="small text-muted"><?= h(tr('انقر لإضافة المنتج', 'Tap to add product')) ?></span>
</div>
<div class="row g-3">
<?php foreach ($catalog as $product): ?>
<div class="col-sm-6 col-lg-4">
<button class="product-tile w-100 text-start" type="button" data-add-product data-sku="<?= h($product['sku']) ?>" data-name="<?= h(current_lang() === 'ar' ? $product['name_ar'] : $product['name_en']) ?>" data-price="<?= h((string) $product['price']) ?>">
<span class="product-pill"><?= h(current_lang() === 'ar' ? $product['unit_ar'] : $product['unit_en']) ?></span>
<div class="fw-semibold mb-1"><?= h(current_lang() === 'ar' ? $product['name_ar'] : $product['name_en']) ?></div>
<div class="small text-muted mb-2">SKU: <?= h($product['sku']) ?></div>
<div class="fw-semibold"><?= h(currency((float) $product['price'])) ?></div>
</button>
</div>
<?php endforeach; ?>
</div>
</div>
</form>
</div>
</div>
<div class="col-xl-5">
<div class="surface-card h-100 cart-panel">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="h5 mb-0"><?= h(tr('السلة', 'Cart')) ?></h3>
<span class="small text-muted" id="cart-count-label">0 <?= h(tr('قطعة', 'items')) ?></span>
</div>
<div class="empty-state compact mb-3" id="cart-empty-state">
<h4><?= h(tr('السلة فارغة', 'Cart is empty')) ?></h4>
<p class="mb-0"><?= h(tr('اختر المنتجات من القائمة اليسرى لبدء أول فاتورة.', 'Select products from the left to start the first receipt.')) ?></p>
</div>
<div id="cart-lines" class="d-grid gap-2"></div>
<div class="cart-summary mt-3 pt-3 border-top">
<div class="d-flex justify-content-between small text-muted mb-2"><span><?= h(tr('المجموع الفرعي', 'Subtotal')) ?></span><span id="cart-subtotal">0.00</span></div>
<div class="d-flex justify-content-between fw-semibold"><span><?= h(tr('الإجمالي', 'Total')) ?></span><span id="cart-total">0.00</span></div>
</div>
<div class="d-grid gap-2 mt-3">
<button class="btn btn-dark" type="submit" form="sale-form"><?= h(tr('حفظ الفاتورة', 'Save invoice')) ?></button>
<button class="btn btn-outline-secondary" type="button" data-clear-cart><?= h(tr('تفريغ السلة', 'Clear cart')) ?></button>
</div>
<div class="alert alert-light border mt-3 mb-0 small">
<?= h(tr('بعد الحفظ ستنتقل مباشرة إلى صفحة التفاصيل ويمكنك الطباعة من هناك.', 'After saving, you will go straight to the detail page and can print from there.')) ?>
</div>
</div>
</div>
</section>
<script>
window.saleCatalog = <?= json_encode(array_values(array_map(static function (array $item): array {
return [
'sku' => $item['sku'],
'name' => current_lang() === 'ar' ? $item['name_ar'] : $item['name_en'],
'price' => (float) $item['price'],
];
}, $catalog)), JSON_UNESCAPED_UNICODE) ?>;
window.saleLabels = <?= json_encode([
'currency' => tr('ر.ع', 'OMR'),
'empty' => tr('السلة فارغة', 'Cart is empty'),
'remove' => tr('إزالة', 'Remove'),
], JSON_UNESCAPED_UNICODE) ?>;
</script>
<?php require __DIR__ . '/footer.php'; ?>

368
index.php
View File

@ -1,150 +1,224 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
require_once __DIR__ . '/includes/app.php';
$user = current_user();
$pageTitle = tr('لوحة التحكم', 'Dashboard');
$activeNav = 'dashboard';
$dbError = null;
$metrics = ['today_sales' => 0, 'today_revenue' => 0.0, 'pos_count' => 0, 'normal_count' => 0, 'recent' => []];
try {
$metrics = dashboard_metrics();
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
require __DIR__ . '/includes/header.php';
?>
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
<?php if (!$user): ?>
<div class="alert alert-info border mb-4 d-flex align-items-center justify-content-between">
<div>
<h5 class="alert-heading mb-1"><i class="bi bi-info-circle me-2"></i><?= h(tr('نسخة استعراضية عامة', 'Public preview mode')) ?></h5>
<p class="mb-0 small"><?= h(tr('يمكنك معاينة الهيكل والتصميم الآن، ثم تسجيل الدخول لتجربة سير العمل الكامل.', 'You can preview the structure and design now, then sign in to try the full workflow.')) ?></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
</body>
</html>
<a class="btn btn-primary" href="<?= h(url_for('login.php')) ?>"><?= h(tr('فتح تسجيل الدخول', 'Open sign in')) ?></a>
</div>
<?php endif; ?>
<?php if ($dbError): ?>
<div class="alert alert-danger mb-4"><i class="bi bi-exclamation-triangle me-2"></i><?= h(tr('تعذر تحميل بيانات قاعدة البيانات حالياً:', 'Database data could not be loaded right now:')) ?> <?= h($dbError) ?></div>
<?php endif; ?>
<!-- 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-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>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['today_sales']) ?></h2>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-success h-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>
<h2 class="display-6 fw-bold mb-0"><?= h(currency((float) $metrics['today_revenue'])) ?></h2>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-info h-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>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['pos_count']) ?></h2>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card text-white bg-warning h-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>
<h2 class="display-6 fw-bold mb-0"><?= h((string) $metrics['normal_count']) ?></h2>
</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>
<div class="card-body p-0">
<?php if (!$metrics['recent']): ?>
<div class="text-center p-5 text-muted">
<i class="bi bi-inbox fs-1 mb-3 d-block text-secondary"></i>
<h5><?= h(tr('لا توجد مبيعات بعد', 'No sales yet')) ?></h5>
<p class="mb-3"><?= h(tr('ابدأ بأول فاتورة من صفحة نقاط البيع.', 'Start your first receipt from the POS page.')) ?></p>
<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">
<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>
</tr>
</thead>
<tbody>
<?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>
</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>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- 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>
<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">
<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">
<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>
</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>
<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>
</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>
<?php endif; ?>
</div>
</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">
<form onsubmit="handleQuickNote(event)">
<div class="modal-header">
<h5 class="modal-title" id="quickNoteModalLabel"><?= 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>
</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>
</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>
</form>
</div>
</div>
</div>
<script>
function handleQuickNote(e) {
e.preventDefault();
var modalEl = document.getElementById('quickNoteModal');
var modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
Swal.fire({
title: '<?= h(tr('تم الحفظ!', 'Saved!')) ?>',
text: '<?= h(tr('تم حفظ الملاحظة بنجاح.', 'Note saved successfully.')) ?>',
icon: 'success',
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000
});
e.target.reset();
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

110
login.php Normal file
View File

@ -0,0 +1,110 @@
<?php
require_once __DIR__ . '/includes/app.php';
if (current_user()) {
redirect_to('index.php');
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim((string) ($_POST['username'] ?? ''));
$password = trim((string) ($_POST['password'] ?? ''));
if ($username === '' || $password === '') {
$error = tr('أدخل اسم المستخدم وكلمة المرور.', 'Enter username and password.');
} elseif (!login_attempt($username, $password)) {
$error = tr('بيانات الدخول غير صحيحة. استخدم أحد الحسابات التجريبية بالأسفل.', 'Invalid credentials. Use one of the demo accounts below.');
} else {
set_flash('success', tr('تم تسجيل الدخول بنجاح.', 'Signed in successfully.'));
redirect_to('index.php');
}
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$projectName = $_SERVER['PROJECT_NAME'] ?? app_name();
$assetVersion = date('YmdHi');
$accounts = demo_users();
?>
<!doctype html>
<html lang="<?= h(current_lang()) ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= h(tr('تسجيل الدخول', 'Sign in')) ?> · <?= h($projectName) ?></title>
<?php if ($projectDescription): ?>
<meta name="description" content='<?= h($projectDescription) ?>' />
<meta property="og:description" content="<?= h($projectDescription) ?>" />
<meta property="twitter:description" content="<?= h($projectDescription) ?>" />
<?php else: ?>
<meta name="description" content="<?= h(tr('تسجيل الدخول إلى مساحة مبيعات حلوى الريامي متعددة الفروع.', 'Sign in to the multi-branch Al Riyami Sweets sales workspace.')) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og:image" content="<?= h($projectImageUrl) ?>" />
<meta property="twitter:image" content="<?= h($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= h($assetVersion) ?>">
</head>
<body class="auth-body">
<main class="auth-shell container-fluid">
<div class="row g-4 align-items-stretch justify-content-center">
<div class="col-lg-5">
<section class="auth-panel h-100">
<div class="eyebrow mb-3"><?= h(tr('MVP جاهز للاستخدام', 'MVP ready to use')) ?></div>
<h1 class="auth-title"><?= h(tr('حلوى الريامي', 'Al Riyami Sweets')) ?></h1>
<p class="auth-subtitle"><?= h(tr('تسجيل دخول ثنائي اللغة مع أدوار منفصلة للمالك ومدير الفرع والكاشير.', 'Bilingual role-based access for owner, branch manager, and cashier.')) ?></p>
<div class="mini-grid mt-4">
<div class="stat-chip"><strong>3</strong><span><?= h(tr('أدوار', 'roles')) ?></span></div>
<div class="stat-chip"><strong>3</strong><span><?= h(tr('فروع', 'branches')) ?></span></div>
<div class="stat-chip"><strong>2</strong><span><?= h(tr('لغات', 'languages')) ?></span></div>
</div>
<div class="alert alert-light border mt-4 mb-0">
<div class="fw-semibold mb-1"><?= h(tr('أول قيمة عملية', 'First practical value')) ?></div>
<div class="small text-muted"><?= h(tr('ابدأ ببيع POS سريع، ثم راجع المبيعات والمخزون والتقارير من نفس الواجهة.', 'Start with a fast POS sale, then review sales, stock, and reports from one workspace.')) ?></div>
</div>
</section>
</div>
<div class="col-lg-4">
<section class="auth-panel h-100">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h4 mb-0"><?= h(tr('تسجيل الدخول', 'Sign in')) ?></h2>
<div class="language-switcher">
<a class="btn btn-sm <?= current_lang() === 'ar' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('login.php', ['lang' => 'ar'])) ?>">AR</a>
<a class="btn btn-sm <?= current_lang() === 'en' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('login.php', ['lang' => 'en'])) ?>">EN</a>
</div>
</div>
<?php if ($error !== ''): ?>
<div class="alert alert-warning"><?= h($error) ?></div>
<?php endif; ?>
<form method="post" class="d-grid gap-3">
<div>
<label class="form-label" for="username"><?= h(tr('اسم المستخدم', 'Username')) ?></label>
<input id="username" name="username" class="form-control form-control-lg" autocomplete="username" required>
</div>
<div>
<label class="form-label" for="password"><?= h(tr('كلمة المرور', 'Password')) ?></label>
<input id="password" name="password" type="password" class="form-control form-control-lg" autocomplete="current-password" required>
</div>
<button class="btn btn-dark btn-lg" type="submit"><?= h(tr('دخول إلى التطبيق', 'Enter app')) ?></button>
</form>
<div class="divider-label"><?= h(tr('حسابات تجريبية', 'Demo accounts')) ?></div>
<div class="d-grid gap-2">
<?php foreach ($accounts as $account): ?>
<button type="button"
class="btn btn-outline-secondary text-start demo-account"
data-username="<?= h($account['username']) ?>"
data-password="<?= h($account['password']) ?>">
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $account['name_ar'] : $account['name_en']) ?></div>
<div class="small text-muted"><?= h(role_label($account['role'])) ?> · <?= h(branch_label($account['branch_code'])) ?></div>
</button>
<?php endforeach; ?>
</div>
</section>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="assets/js/main.js?v=<?= h($assetVersion) ?>"></script>
</body>
</html>

5
logout.php Normal file
View File

@ -0,0 +1,5 @@
<?php
require_once __DIR__ . '/includes/app.php';
logout_user();
set_flash('success', tr('تم تسجيل الخروج.', 'Signed out successfully.'));
redirect_to('login.php');

3
normal_sale.php Normal file
View File

@ -0,0 +1,3 @@
<?php
$saleMode = 'normal';
require_once __DIR__ . '/includes/sale_form.php';

3
pos.php Normal file
View File

@ -0,0 +1,3 @@
<?php
$saleMode = 'pos';
require_once __DIR__ . '/includes/sale_form.php';

140
purchases.php Normal file
View File

@ -0,0 +1,140 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager']);
$pageTitle = tr('المشتريات', 'Purchases');
$activeNav = 'purchases';
$allPurchases = purchase_pipeline();
// Search logic
$search = $_GET['q'] ?? '';
$filteredPurchases = [];
if ($search) {
$lowerSearch = mb_strtolower($search);
foreach ($allPurchases as $key => $row) {
if (
str_contains(mb_strtolower($row['supplier']), $lowerSearch) ||
str_contains(mb_strtolower($row['reference']), $lowerSearch)
) {
$filteredPurchases[$key] = $row;
}
}
} else {
$filteredPurchases = $allPurchases;
}
// Pagination logic
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$total = count($filteredPurchases);
$totalPages = max(1, ceil($total / $limit));
$offset = ($page - 1) * $limit;
$purchaseRows = array_slice($filteredPurchases, $offset, $limit, true);
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<div class="row g-4 align-items-center mb-3">
<div class="col-lg-8">
<h3 class="h5 mb-2"><i class="bi bi-bag-plus me-2"></i><?= h(tr('لوحة استلام الموردين', 'Supplier receiving board')) ?></h3>
<p class="text-muted mb-0"><?= h(tr('هذه صفحة تمهيدية منظمة للمشتريات حتى تكون كل وحدة في صفحة مستقلة من البداية.', 'This is a structured starter page for purchases so every module already has a dedicated screen.')) ?></p>
</div>
<div class="col-lg-4 text-lg-end">
<button type="button" class="btn btn-primary" onclick="mockEdit()">
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة أمر شراء', 'Add Purchase Order')) ?>
</button>
</div>
</div>
<form class="d-flex mb-3" method="GET" action="purchases.php">
<div class="input-group" style="max-width: 400px;">
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث...', 'Search...')) ?>" value="<?= h($search) ?>">
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</div>
</form>
</section>
<section class="surface-card">
<div class="table-responsive">
<table class="table app-table align-middle mb-0">
<thead>
<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>
</tr>
</thead>
<tbody>
<?php if(empty($purchaseRows)): ?>
<tr><td colspan="6" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<?php endif; ?>
<?php foreach ($purchaseRows as $row): ?>
<tr>
<td><?= h($row['supplier']) ?></td>
<td><?= h($row['reference']) ?></td>
<td><?= h(branch_label($row['branch'])) ?></td>
<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')) ?>">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-light text-danger border" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('purchases.php', ['p' => $i, 'q' => $search])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
</section>
<script>
function mockEdit() {
Swal.fire({
title: '<?= h(tr('تعديل (تجريبي)', 'Edit (Demo)')) ?>',
text: '<?= h(tr('هذه الميزة غير مفعلة للبيانات التجريبية.', 'This feature is mock data and not active yet.')) ?>',
icon: 'info',
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>'
});
}
function mockDelete() {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire(
'<?= h(tr('محذوف!', 'Deleted!')) ?>',
'<?= h(tr('حساب تجريبي لا يمكن حذفه.', 'Demo account cannot be deleted.')) ?>',
'success'
);
}
});
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

52
reports.php Normal file
View File

@ -0,0 +1,52 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager']);
$pageTitle = tr('التقارير', 'Reports');
$activeNav = 'reports';
$dbError = null;
$report = ['gross' => 0.0, 'branch_totals' => [], 'payment_totals' => [], 'product_totals' => [], 'sales_count' => 0];
try {
$report = report_metrics();
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
require __DIR__ . '/includes/header.php';
?>
<section class="row g-3 mb-4">
<div class="col-md-4"><article class="metric-card"><div class="eyebrow"><?= h(tr('إجمالي المبيعات', 'Gross sales')) ?></div><div class="metric-value"><?= h(currency((float) $report['gross'])) ?></div><div class="small text-muted"><?= h(tr('حسب نطاق صلاحية المستخدم الحالي', 'Scoped to the current viewer permissions')) ?></div></article></div>
<div class="col-md-4"><article class="metric-card"><div class="eyebrow"><?= h(tr('عدد الفواتير', 'Invoices')) ?></div><div class="metric-value"><?= h((string) $report['sales_count']) ?></div><div class="small text-muted"><?= h(tr('إجمالي الفواتير المسجلة', 'Total logged invoices')) ?></div></article></div>
<div class="col-md-4"><article class="metric-card"><div class="eyebrow"><?= h(tr('أفضل صنف', 'Top product')) ?></div><div class="metric-value small-metric"><?= h($report['product_totals'] ? product_label((string) array_key_first($report['product_totals'])) : tr('لا يوجد', 'None yet')) ?></div><div class="small text-muted"><?= h(tr('الأكثر مبيعاً حتى الآن', 'Most sold item so far')) ?></div></article></div>
</section>
<section class="row g-4">
<div class="col-lg-6">
<div class="surface-card h-100">
<h3 class="h5 mb-3"><?= h(tr('المبيعات حسب الفرع', 'Sales by branch')) ?></h3>
<?php if ($dbError): ?>
<div class="alert alert-warning"><?= h($dbError) ?></div>
<?php elseif (!$report['branch_totals']): ?>
<div class="empty-state compact"><h4><?= h(tr('لا توجد بيانات', 'No data')) ?></h4><p><?= h(tr('أضف عملية بيع أولاً لبدء التقارير.', 'Add a first sale to activate reports.')) ?></p></div>
<?php else: ?>
<div class="d-grid gap-2">
<?php foreach ($report['branch_totals'] as $branchCode => $amount): ?>
<div class="report-row"><span><?= h(branch_label((string) $branchCode)) ?></span><strong><?= h(currency((float) $amount)) ?></strong></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<div class="col-lg-6">
<div class="surface-card h-100">
<h3 class="h5 mb-3"><?= h(tr('المبيعات حسب الدفع', 'Sales by payment')) ?></h3>
<?php if (!$report['payment_totals']): ?>
<div class="empty-state compact"><h4><?= h(tr('بانتظار البيانات', 'Waiting for data')) ?></h4><p><?= h(tr('عند تسجيل عمليات بيع ستظهر هنا طرق الدفع.', 'Payment mix will appear here once sales are logged.')) ?></p></div>
<?php else: ?>
<div class="d-grid gap-2">
<?php foreach ($report['payment_totals'] as $payment => $amount): ?>
<div class="report-row"><span><?= h(ucfirst((string) $payment)) ?></span><strong><?= h(currency((float) $amount)) ?></strong></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</section>
<?php require __DIR__ . '/includes/footer.php'; ?>

72
sale.php Normal file
View File

@ -0,0 +1,72 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$pageTitle = tr('تفاصيل الفاتورة', 'Sale Detail');
$activeNav = 'sales';
$id = (int) ($_GET['id'] ?? 0);
$sale = null;
$dbError = null;
if ($id > 0) {
try {
$sale = fetch_sale($id);
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
}
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<?php if ($dbError): ?>
<div class="alert alert-warning"><?= h($dbError) ?></div>
<?php elseif (!$sale): ?>
<div class="empty-state">
<h4><?= h(tr('الفاتورة غير موجودة', 'Sale not found')) ?></h4>
<p><?= h(tr('قد تكون الفاتورة خارج صلاحية هذا الحساب أو لم تعد موجودة.', 'The sale may be outside this account scope or no longer exists.')) ?></p>
<a class="btn btn-outline-secondary" href="<?= h(url_for('sales.php')) ?>"><?= h(tr('العودة إلى المبيعات', 'Back to sales')) ?></a>
</div>
<?php else: ?>
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap mb-4">
<div>
<div class="eyebrow"><?= h(sale_mode_label((string) $sale['sale_mode'])) ?></div>
<h3 class="h4 mb-1"><?= h($sale['receipt_no']) ?></h3>
<div class="small text-muted"><?= h(branch_label((string) $sale['branch_code'])) ?> · <?= h(date('Y-m-d H:i', strtotime((string) $sale['sale_date']))) ?></div>
</div>
<div class="d-flex gap-2 flex-wrap">
<button type="button" class="btn btn-dark" data-print-page><?= h(tr('طباعة الإيصال', 'Print receipt')) ?></button>
<a class="btn btn-outline-secondary" href="<?= h(url_for('sales.php')) ?>"><?= h(tr('العودة للسجل', 'Back to ledger')) ?></a>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-3"><div class="detail-card"><span><?= h(tr('العميل', 'Customer')) ?></span><strong><?= h((string) ($sale['customer_name'] ?: tr('دون اسم', 'Walk-in'))) ?></strong></div></div>
<div class="col-md-3"><div class="detail-card"><span><?= h(tr('الدفع', 'Payment')) ?></span><strong><?= h(ucfirst((string) $sale['payment_method'])) ?></strong></div></div>
<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>
</tr>
</thead>
<tbody>
<?php foreach ($sale['items'] as $item): ?>
<tr>
<td><?= h(current_lang() === 'ar' ? ($item['name_ar'] ?? $item['sku']) : ($item['name_en'] ?? $item['sku'])) ?></td>
<td><?= h((string) ($item['qty'] ?? 0)) ?></td>
<td><?= h(currency((float) ($item['price'] ?? 0))) ?></td>
<td><?= h(currency((float) ($item['line_total'] ?? 0))) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</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; ?>
<?php endif; ?>
</section>
<?php require __DIR__ . '/includes/footer.php'; ?>

191
sales.php Normal file
View File

@ -0,0 +1,191 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$pageTitle = tr('المبيعات', 'Sales Ledger');
$activeNav = 'sales';
$mode = isset($_GET['mode']) && in_array($_GET['mode'], ['pos', 'normal'], true) ? $_GET['mode'] : null;
$branch = isset($_GET['branch']) && array_key_exists($_GET['branch'], branches()) ? $_GET['branch'] : null;
$search = $_GET['q'] ?? '';
$dbError = null;
$sales = [];
$totalPages = 1;
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
try {
ensure_sales_table();
$params = [];
$where = ' WHERE 1=1 ';
if ($mode) {
$where .= ' AND sale_mode = :sale_mode ';
$params[':sale_mode'] = $mode;
}
if ($branch) {
$where .= ' AND branch_code = :branch_code ';
$params[':branch_code'] = $branch;
}
if ($user && $user['role'] !== 'owner') {
$where .= ' AND branch_code = :viewer_branch ';
$params[':viewer_branch'] = $user['branch_code'];
}
if ($search) {
$where .= ' AND (receipt_no LIKE :search OR cashier_name LIKE :search OR customer_name LIKE :search)';
$params[':search'] = "%$search%";
}
// Pagination counts
$countSql = 'SELECT COUNT(*) FROM sales_orders' . $where;
$countStmt = db()->prepare($countSql);
foreach ($params as $key => $value) {
$countStmt->bindValue($key, $value);
}
$countStmt->execute();
$total = $countStmt->fetchColumn();
$totalPages = max(1, ceil($total / $limit));
// Fetch Data
$sql = 'SELECT * FROM sales_orders' . $where . ' ORDER BY sale_date DESC LIMIT :limit OFFSET :offset';
$stmt = db()->prepare($sql);
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value);
}
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$sales = $stmt->fetchAll();
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-3">
<div>
<h3 class="h5 mb-1"><i class="bi bi-journal-text me-2"></i><?= h(tr('سجل الفواتير', 'Invoice ledger')) ?></h3>
<div class="small text-muted"><?= h(tr('ابحث بصرياً في أحدث المبيعات مع صلاحيات حسب الدور والفرع.', 'Scan the latest sales with role and branch scoping.')) ?></div>
</div>
<div class="d-flex gap-2 flex-wrap">
<a class="btn btn-sm <?= $mode === null ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('sales.php', ['q' => $search])) ?>"><?= h(tr('الكل', 'All')) ?></a>
<a class="btn btn-sm <?= $mode === 'pos' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('sales.php', ['mode' => 'pos', 'q' => $search])) ?>">POS</a>
<a class="btn btn-sm <?= $mode === 'normal' ? 'btn-dark' : 'btn-outline-secondary' ?>" href="<?= h(url_for('sales.php', ['mode' => 'normal', 'q' => $search])) ?>"><?= h(tr('بيع عادي', 'Normal')) ?></a>
</div>
</div>
<form class="d-flex mb-3" method="GET" action="sales.php">
<?php if($mode): ?>
<input type="hidden" name="mode" value="<?= h($mode) ?>">
<?php endif; ?>
<div class="input-group" style="max-width: 400px;">
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث بالإيصال أو الكاشير...', 'Search receipt or cashier...')) ?>" value="<?= h($search) ?>">
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</div>
</form>
<?php if ($dbError): ?>
<div class="alert alert-warning"><?= h($dbError) ?></div>
<?php elseif (!$sales): ?>
<div class="empty-state">
<h4><?= h(tr('لا توجد نتائج', 'No sales found')) ?></h4>
<p><?= h(tr('جرّب إنشاء فاتورة جديدة من صفحة البيع.', 'Try creating a new invoice from the sale page.')) ?></p>
<div class="d-flex gap-2 justify-content-center flex-wrap">
<a class="btn btn-dark" href="<?= h(url_for('pos.php')) ?>">POS</a>
<a class="btn btn-outline-secondary" href="<?= h(url_for('normal_sale.php')) ?>"><?= h(tr('بيع عادي', 'Normal Sale')) ?></a>
</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>
</tr>
</thead>
<tbody>
<?php foreach ($sales as $sale): ?>
<tr>
<td>
<div class="fw-semibold"><?= h($sale['receipt_no']) ?></div>
<div class="small text-muted"><?= h((string) $sale['item_count']) ?> <?= h(tr('قطعة', 'items')) ?></div>
</td>
<td><?= h(branch_label((string) $sale['branch_code'])) ?></td>
<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 class="fw-semibold"><?= h(currency((float) $sale['total_amount'])) ?></td>
<td><?= h(date('Y-m-d H:i', strtotime((string) $sale['sale_date']))) ?></td>
<td>
<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')) ?>">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-light text-danger border" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('sales.php', ['p' => $i, 'q' => $search, 'mode' => $mode])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
<?php endif; ?>
</section>
<script>
function mockEdit() {
Swal.fire({
title: '<?= h(tr('تعديل (غير متاح)', 'Edit (Disabled)')) ?>',
text: '<?= h(tr('تعديل الفواتير غير متاح لأسباب محاسبية.', 'Invoice editing is disabled for accounting reasons.')) ?>',
icon: 'info',
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>'
});
}
function mockDelete() {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('حذف الفاتورة قد يؤثر على حسابات المخزون. (هذه الميزة تجريبية حالياً)', "Deleting an invoice might affect stock. (This is currently mock data)")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire(
'<?= h(tr('محذوف!', 'Deleted!')) ?>',
'<?= h(tr('لم يتم الحذف فعلياً.', 'Not actually deleted.')) ?>',
'success'
);
}
});
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

276
stock.php Normal file
View File

@ -0,0 +1,276 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager', 'cashier']);
$pageTitle = tr('المخزون', 'Stock');
$activeNav = 'stock';
$dbError = null;
$allStock = [];
try {
$allStock = stock_snapshot();
} catch (Throwable $e) {
$dbError = $e->getMessage();
}
$categories = [];
$suppliers = [];
try {
$pdo = db();
$categories = $pdo->query('SELECT id, name_ar, name_en FROM categories ORDER BY name_ar ASC')->fetchAll();
$suppliers = $pdo->query('SELECT id, name FROM suppliers ORDER BY name ASC')->fetchAll();
} catch (Throwable $e) {
// Ignore if not present
}
// Search logic
$search = $_GET['q'] ?? '';
$filteredStock = [];
if ($search && empty($dbError)) {
$lowerSearch = mb_strtolower($search);
foreach ($allStock as $key => $row) {
if (
str_contains(mb_strtolower($row['sku']), $lowerSearch) ||
str_contains(mb_strtolower($row['name']), $lowerSearch)
) {
$filteredStock[$key] = $row;
}
}
} else {
$filteredStock = $allStock;
}
// Pagination logic
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$total = count($filteredStock);
$totalPages = max(1, ceil($total / $limit));
$offset = ($page - 1) * $limit;
$stockRows = array_slice($filteredStock, $offset, $limit, true);
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<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>
</div>
<div class="col-lg-4 text-lg-end">
<button type="button" class="btn btn-primary" onclick="openItemModal()">
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة صنف', 'Add Item')) ?>
</button>
</div>
</div>
<form class="d-flex mb-3" method="GET" action="stock.php">
<div class="input-group" style="max-width: 400px;">
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث برمز الصنف أو الاسم...', 'Search by SKU or name...')) ?>" value="<?= h($search) ?>">
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</div>
</form>
</section>
<section class="surface-card">
<?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>
<tr>
<th>SKU</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>
</tr>
</thead>
<tbody>
<?php if(empty($stockRows)): ?>
<tr><td colspan="8" class="text-center text-muted py-4"><?= 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>
<td>
<?php if ($row['available'] <= 12): ?>
<span class="badge text-bg-warning"><?= h(tr('منخفض', 'Low')) ?></span>
<?php else: ?>
<span class="badge text-bg-light border"><?= h(tr('مستقر', 'Stable')) ?></span>
<?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']) ?>')" 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')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('stock.php', ['p' => $i, 'q' => $search])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
<?php endif; ?>
</section>
<!-- Item Modal -->
<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)">
<div class="modal-header">
<h5 class="modal-title" id="itemModalLabel"><?= h(tr('إضافة / تعديل صنف', 'Add / Edit Item')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<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/*">
</div>
<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">
<button type="button" class="btn btn-outline-secondary" onclick="suggestSKU()" title="<?= h(tr('اقتراح رمز', 'Suggest SKU')) ?>">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('اسم الصنف', 'Product Name')) ?></label>
<input type="text" class="form-control" id="item_name" required>
</div>
<div class="row mb-3">
<div class="col-4">
<label class="form-label"><?= h(tr('السعر', 'Price')) ?></label>
<input type="number" step="0.01" class="form-control" id="item_price" required>
</div>
<div class="col-4">
<label class="form-label"><?= h(tr('الرصيد الافتتاحي', 'Opening Stock')) ?></label>
<input type="number" class="form-control" id="item_base_stock" required>
</div>
<div class="col-4">
<label class="form-label"><?= h(tr('الضريبة (VAT %)', 'VAT %')) ?></label>
<input type="number" step="0.01" class="form-control" id="item_vat" value="5" required>
</div>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('التصنيف', 'Category')) ?></label>
<select class="form-select" id="item_category" required>
<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>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('المورد', 'Supplier')) ?></label>
<select class="form-select" id="item_supplier" required>
<option value=""><?= h(tr('-- اختر المورد --', '-- Select Supplier --')) ?></option>
<?php foreach($suppliers as $sup): ?>
<option value="<?= h($sup['id']) ?>"><?= h($sup['name']) ?></option>
<?php endforeach; ?>
</select>
</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 Changes')) ?></button>
</div>
</form>
</div>
</div>
</div>
<script>
const existingSkus = <?= json_encode(array_values(array_unique(array_map('strval', array_column($allStock, 'sku'))))) ?>;
function suggestSKU() {
let newSku;
do {
newSku = Math.floor(10000000 + Math.random() * 90000000).toString().substring(0, 8);
} while (existingSkus.includes(newSku));
document.getElementById('item_sku').value = newSku;
}
let itemModalObj = null;
document.addEventListener('DOMContentLoaded', function () {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
itemModalObj = new bootstrap.Modal(document.getElementById('itemModal'));
});
function openItemModal(sku = '', name = '', price = '', base_stock = '') {
document.getElementById('item_sku').value = sku;
document.getElementById('item_name').value = name;
document.getElementById('item_price').value = price;
document.getElementById('item_base_stock').value = base_stock;
document.getElementById('item_vat').value = '5';
document.getElementById('item_picture').value = '';
document.getElementById('item_category').value = '';
document.getElementById('item_supplier').value = '';
itemModalObj.show();
}
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')) ?>'
});
}
function mockDelete() {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire(
'<?= h(tr('محذوف!', 'Deleted!')) ?>',
'<?= h(tr('حساب تجريبي لا يمكن حذفه.', 'Demo data cannot be deleted.')) ?>',
'success'
);
}
});
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

251
suppliers.php Normal file
View File

@ -0,0 +1,251 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_auth();
$pageTitle = tr('الموردون', 'Suppliers');
$activeNav = 'suppliers';
$pdo = db();
// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create') {
$stmt = $pdo->prepare('INSERT INTO suppliers (name, contact_person, phone, email, address) VALUES (?, ?, ?, ?, ?)');
$stmt->execute([$_POST['name'], $_POST['contact_person'] ?? '', $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '']);
set_flash('success', tr('تمت إضافة المورد بنجاح', 'Supplier added successfully'));
redirect_to('suppliers.php');
} elseif ($action === 'edit') {
$stmt = $pdo->prepare('UPDATE suppliers SET name = ?, contact_person = ?, phone = ?, email = ?, address = ? WHERE id = ?');
$stmt->execute([$_POST['name'], $_POST['contact_person'] ?? '', $_POST['phone'] ?? '', $_POST['email'] ?? '', $_POST['address'] ?? '', $_POST['id']]);
set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully'));
redirect_to('suppliers.php');
} elseif ($action === 'delete') {
$stmt = $pdo->prepare('DELETE FROM suppliers WHERE id = ?');
$stmt->execute([$_POST['id']]);
set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully'));
redirect_to('suppliers.php');
}
}
// Pagination & Search
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
$search = $_GET['q'] ?? '';
$where = '1=1';
$params = [];
if ($search) {
$where .= ' AND (name LIKE ? OR phone LIKE ? OR email LIKE ?)';
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM suppliers WHERE $where");
$totalStmt->execute($params);
$total = $totalStmt->fetchColumn();
$totalPages = ceil($total / $limit);
$queryStmt = $pdo->prepare("SELECT * FROM suppliers WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
$queryStmt->execute($params);
$items = $queryStmt->fetchAll();
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h3 class="h5 mb-2"><i class="bi bi-truck me-2"></i><?= h($pageTitle) ?></h3>
<p class="text-muted mb-0"><?= h(tr('إدارة حسابات الموردين', 'Manage supplier accounts')) ?></p>
</div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
<i class="bi bi-plus-lg"></i> <?= h(tr('إضافة مورد', 'Add Supplier')) ?>
</button>
</div>
<form class="d-flex mb-3" method="GET" action="suppliers.php">
<div class="input-group" style="max-width: 400px;">
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث بالاسم أو الهاتف...', 'Search name or phone...')) ?>" value="<?= h($search) ?>">
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</div>
</form>
</section>
<section class="surface-card">
<div class="table-responsive">
<table class="table app-table align-middle mb-0">
<thead>
<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>
</tr>
</thead>
<tbody>
<?php if(empty($items)): ?>
<tr><td colspan="6" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<?php endif; ?>
<?php foreach ($items as $item): ?>
<tr>
<td><?= h($item['id']) ?></td>
<td class="fw-semibold"><?= h($item['name']) ?></td>
<td><?= h($item['contact_person']) ?></td>
<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')) ?>">
<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')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('suppliers.php', ['p' => $i, 'q' => $search])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
</section>
<!-- Add Modal -->
<div class="modal fade" id="addModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="suppliers.php">
<input type="hidden" name="action" value="create">
<div class="modal-header">
<h5 class="modal-title"><?= h(tr('إضافة مورد', 'Add Supplier')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم', 'Name')) ?></label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('الشخص المسؤول', 'Contact Person')) ?></label>
<input type="text" name="contact_person" class="form-control">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الهاتف', 'Phone')) ?></label>
<input type="text" name="phone" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('البريد الإلكتروني', 'Email')) ?></label>
<input type="email" name="email" class="form-control">
</div>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('العنوان', 'Address')) ?></label>
<textarea name="address" class="form-control"></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>
</form>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="suppliers.php">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="id" id="edit_id">
<div class="modal-header">
<h5 class="modal-title"><?= h(tr('تعديل', 'Edit')) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label"><?= h(tr('الاسم', 'Name')) ?></label>
<input type="text" name="name" id="edit_name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('الشخص المسؤول', 'Contact Person')) ?></label>
<input type="text" name="contact_person" id="edit_contact_person" class="form-control">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('الهاتف', 'Phone')) ?></label>
<input type="text" name="phone" id="edit_phone" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label"><?= h(tr('البريد الإلكتروني', 'Email')) ?></label>
<input type="email" name="email" id="edit_email" class="form-control">
</div>
</div>
<div class="mb-3">
<label class="form-label"><?= h(tr('العنوان', 'Address')) ?></label>
<textarea name="address" id="edit_address" class="form-control"></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>
</form>
</div>
</div>
</div>
<!-- Delete Form -->
<form id="deleteForm" method="POST" action="suppliers.php" style="display:none;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" id="delete_id">
</form>
<script>
function editItem(item) {
document.getElementById('edit_id').value = item.id;
document.getElementById('edit_name').value = item.name;
document.getElementById('edit_contact_person').value = item.contact_person || '';
document.getElementById('edit_phone').value = item.phone || '';
document.getElementById('edit_email').value = item.email || '';
document.getElementById('edit_address').value = item.address || '';
new bootstrap.Modal(document.getElementById('editModal')).show();
}
function deleteItem(id) {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => {
if (result.isConfirmed) {
document.getElementById('delete_id').value = id;
document.getElementById('deleteForm').submit();
}
});
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>

203
users.php Normal file
View File

@ -0,0 +1,203 @@
<?php
require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner']);
$pageTitle = tr('المستخدمون والأدوار', 'Users & Roles');
$activeNav = 'users';
$allAccounts = demo_users();
// Search logic
$search = $_GET['q'] ?? '';
$filteredAccounts = [];
if ($search) {
$lowerSearch = mb_strtolower($search);
foreach ($allAccounts as $key => $acc) {
if (
str_contains(mb_strtolower($acc['name_ar']), $lowerSearch) ||
str_contains(mb_strtolower($acc['name_en']), $lowerSearch) ||
str_contains(mb_strtolower($acc['username']), $lowerSearch)
) {
$filteredAccounts[$key] = $acc;
}
}
} else {
$filteredAccounts = $allAccounts;
}
// Pagination logic
$page = max(1, (int)($_GET['p'] ?? 1));
$limit = 10;
$total = count($filteredAccounts);
$totalPages = max(1, ceil($total / $limit));
$offset = ($page - 1) * $limit;
$accounts = array_slice($filteredAccounts, $offset, $limit, true);
require __DIR__ . '/includes/header.php';
?>
<section class="surface-card mb-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h3 class="h5 mb-2"><i class="bi bi-people me-2"></i><?= h(tr('الوصول حسب الدور', 'Role-based access')) ?></h3>
<p class="text-muted mb-0"><?= h(tr('الإصدار الأول يستخدم حسابات تجريبية منفصلة لإثبات هيكل الصلاحيات قبل ربط المستخدمين بقاعدة البيانات.', 'This first version uses separate demo accounts to prove the permissions model before wiring users into the database.')) ?></p>
</div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="bi bi-person-plus"></i> <?= h(tr('إضافة مستخدم', 'Add User')) ?>
</button>
</div>
<form class="d-flex mb-3" method="GET" action="users.php">
<div class="input-group" style="max-width: 400px;">
<input type="text" name="q" class="form-control" placeholder="<?= h(tr('بحث...', 'Search...')) ?>" value="<?= h($search) ?>">
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
</div>
</form>
</section>
<section class="surface-card">
<div class="table-responsive">
<table class="table app-table align-middle mb-0">
<thead>
<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>
</tr>
</thead>
<tbody>
<?php if(empty($accounts)): ?>
<tr><td colspan="7" class="text-center text-muted py-4"><?= h(tr('لا توجد بيانات', 'No data found')) ?></td></tr>
<?php endif; ?>
<?php foreach ($accounts as $key => $account): ?>
<tr>
<td>
<div class="fw-semibold"><?= h(current_lang() === 'ar' ? $account['name_ar'] : $account['name_en']) ?></div>
<div class="small text-muted"><?= h($account['username']) ?></div>
</td>
<td><?= h(role_label($account['role'])) ?></td>
<td><?= h(branch_label($account['branch_code'])) ?></td>
<td><span class="badge text-bg-light border"><?= h(tr('نعم', 'Yes')) ?></span></td>
<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')) ?>">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-light text-danger border" onclick="mockDelete()" title="<?= h(tr('حذف', 'Delete')) ?>">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center mb-0">
<?php for($i=1; $i<=$totalPages; $i++): ?>
<li class="page-item <?= $i === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= h(url_for('users.php', ['p' => $i, 'q' => $search])) ?>"><?= $i ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
</section>
<!-- Add User Modal -->
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form id="addUserForm" onsubmit="handleUserSubmit(event)">
<div class="modal-header">
<h5 class="modal-title" id="addUserModalLabel"><?= h(tr('إضافة مستخدم جديد', 'Add New User')) ?></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="userName" class="form-label"><?= h(tr('اسم المستخدم', 'Name')) ?></label>
<input type="text" class="form-control" id="userName" required>
</div>
<div class="mb-3">
<label for="userRole" class="form-label"><?= h(tr('الدور', 'Role')) ?></label>
<select class="form-select" id="userRole" required>
<option value="cashier"><?= h(tr('كاشير', 'Cashier')) ?></option>
<option value="manager"><?= h(tr('مدير فرع', 'Branch Manager')) ?></option>
<option value="owner"><?= h(tr('مالك', 'Owner')) ?></option>
</select>
</div>
<div class="mb-3">
<label for="userBranch" class="form-label"><?= h(tr('الفرع', 'Branch')) ?></label>
<select class="form-select" id="userBranch" required>
<option value="main"><?= h(tr('الرئيسي', 'Main')) ?></option>
<option value="north"><?= h(tr('الشمالي', 'North')) ?></option>
<option value="south"><?= h(tr('الجنوبي', 'South')) ?></option>
</select>
</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>
</form>
</div>
</div>
</div>
<script>
function handleUserSubmit(e) {
e.preventDefault();
// Close the modal
var myModalEl = document.getElementById('addUserModal');
var modal = bootstrap.Modal.getInstance(myModalEl);
modal.hide();
// Show SweetAlert2 Success Message
Swal.fire({
title: '<?= h(tr('تم الحفظ!', 'Saved!')) ?>',
text: '<?= h(tr('تمت إضافة المستخدم بنجاح. (تجريبي)', 'User has been added successfully. (Demo)')) ?>',
icon: 'success',
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>'
});
// Reset form
e.target.reset();
}
function mockEdit() {
Swal.fire({
title: '<?= h(tr('تعديل (تجريبي)', 'Edit (Demo)')) ?>',
text: '<?= h(tr('هذه الميزة غير مفعلة للبيانات التجريبية.', 'This feature is mock data and not active yet.')) ?>',
icon: 'info',
confirmButtonText: '<?= h(tr('حسناً', 'OK')) ?>'
});
}
function mockDelete() {
Swal.fire({
title: '<?= h(tr('هل أنت متأكد؟', 'Are you sure?')) ?>',
text: '<?= h(tr('لن تتمكن من التراجع عن هذا!', "You won't be able to revert this!")) ?>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#dc3545',
cancelButtonColor: '#6c757d',
confirmButtonText: '<?= h(tr('نعم، احذف', 'Yes, delete it!')) ?>',
cancelButtonText: '<?= h(tr('إلغاء', 'Cancel')) ?>'
}).then((result) => {
if (result.isConfirmed) {
Swal.fire(
'<?= h(tr('محذوف!', 'Deleted!')) ?>',
'<?= h(tr('حساب تجريبي لا يمكن حذفه.', 'Demo account cannot be deleted.')) ?>',
'success'
);
}
});
}
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>