Autosave: 20260302-103011

This commit is contained in:
Flatlogic Bot 2026-03-02 10:30:11 +00:00
parent 7406216157
commit 874d750960
22 changed files with 2998 additions and 487 deletions

310
admin.php
View File

@ -1,166 +1,170 @@
<?php
require_once __DIR__ . '/db/config.php';
$title = 'dashboard';
require_once __DIR__ . '/includes/header.php';
// Simple handling of form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action']) && $_POST['action'] === 'add') {
$keywords = $_POST['keywords'] ?? '';
$answer = $_POST['answer'] ?? '';
if ($keywords && $answer) {
$stmt = db()->prepare("INSERT INTO faqs (keywords, answer) VALUES (?, ?)");
$stmt->execute([$keywords, $answer]);
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'delete') {
$id = $_POST['id'] ?? 0;
if ($id) {
$stmt = db()->prepare("DELETE FROM faqs WHERE id = ?");
$stmt->execute([$id]);
}
} elseif (isset($_POST['action']) && $_POST['action'] === 'update_settings') {
$token = $_POST['telegram_token'] ?? '';
$stmt = db()->prepare("INSERT INTO settings (setting_key, setting_value) VALUES ('telegram_token', ?) ON DUPLICATE KEY UPDATE setting_value = ?");
$stmt->execute([$token, $token]);
}
header("Location: admin.php");
exit;
}
// Stats logic
$branch_id = $_SESSION['branch_id'];
$stats = [
'today_revenue' => 0,
'active_orders' => 0,
'new_customers' => 0,
'ready_orders' => 0,
];
$faqs = db()->query("SELECT * FROM faqs ORDER BY created_at DESC")->fetchAll();
$messages = db()->query("SELECT * FROM messages ORDER BY created_at DESC LIMIT 50")->fetchAll();
// Today's revenue
$stmt = db()->prepare("SELECT SUM(amount) as total FROM payments WHERE created_at >= CURDATE() AND order_id IN (SELECT id FROM orders WHERE branch_id = ?)");
$stmt->execute([$branch_id]);
$stats['today_revenue'] = $stmt->fetch()['total'] ?? 0;
// Active orders (received, processing)
$stmt = db()->prepare("SELECT COUNT(*) as count FROM orders WHERE branch_id = ? AND status IN ('received', 'processing')");
$stmt->execute([$branch_id]);
$stats['active_orders'] = $stmt->fetch()['count'] ?? 0;
// Ready orders
$stmt = db()->prepare("SELECT COUNT(*) as count FROM orders WHERE branch_id = ? AND status = 'ready'");
$stmt->execute([$branch_id]);
$stats['ready_orders'] = $stmt->fetch()['count'] ?? 0;
// New customers today
$stmt = db()->prepare("SELECT COUNT(*) as count FROM customers WHERE branch_id = ? AND created_at >= CURDATE()");
$stmt->execute([$branch_id]);
$stats['new_customers'] = $stmt->fetch()['count'] ?? 0;
// Recent orders
$stmt = db()->prepare("SELECT o.*, c.name_en as customer_name_en, c.name_ar as customer_name_ar
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE o.branch_id = ?
ORDER BY o.created_at DESC LIMIT 5");
$stmt->execute([$branch_id]);
$recent_orders = $stmt->fetchAll();
$telegramToken = '';
$stmt = db()->query("SELECT setting_value FROM settings WHERE setting_key = 'telegram_token'");
$row = $stmt->fetch();
if ($row) {
$telegramToken = $row['setting_value'];
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin - FAQ Manager</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<style>
.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;
}
</style>
</head>
<body>
<div class="bg-animations">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
</div>
<div class="admin-container">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h1>FAQ Manager</h1>
<a href="index.php" class="admin-link">Back to Chat</a>
</div>
<div class="admin-card" style="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);">
<h3 style="margin-top: 0; margin-bottom: 1.5rem; font-weight: 700;">Telegram Bot Settings</h3>
<form method="POST">
<input type="hidden" name="action" value="update_settings">
<div class="form-group">
<label for="telegram_token">Telegram Bot Token</label>
<input type="text" name="telegram_token" id="telegram_token" class="form-control" placeholder="Paste your bot token from @BotFather" value="<?= htmlspecialchars($telegramToken) ?>">
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card p-3">
<div class="d-flex align-items-center">
<div class="bg-primary text-white p-3 rounded-4 me-3">
<i class="bi bi-cash-stack fs-3"></i>
</div>
<div>
<div class="text-muted small"><?= __('today_revenue') ?? 'Today Revenue' ?></div>
<div class="fw-bold fs-5"><?= number_format($stats['today_revenue'], 2) ?> SAR</div>
</div>
</div>
<p style="font-size: 0.85em; color: #555; margin-top: 0.5rem;">
Webhook URL: <code>https://<?= $_SERVER['HTTP_HOST'] ?>/api/telegram_webhook.php</code>
</p>
<button type="submit" class="btn-add" style="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;">Save Token</button>
</form>
</div>
</div>
<div class="admin-card" style="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);">
<h3 style="margin-top: 0; margin-bottom: 1.5rem; font-weight: 700;">Add New FAQ</h3>
<form method="POST">
<input type="hidden" name="action" value="add">
<div class="form-group">
<label for="keywords">Keywords (comma separated)</label>
<input type="text" name="keywords" id="keywords" class="form-control" placeholder="e.g. price, cost, dollar" required>
<div class="col-md-3">
<div class="card p-3">
<div class="d-flex align-items-center">
<div class="bg-warning text-white p-3 rounded-4 me-3">
<i class="bi bi-clock-history fs-3"></i>
</div>
<div>
<div class="text-muted small"><?= __('active_orders') ?></div>
<div class="fw-bold fs-5"><?= $stats['active_orders'] ?></div>
</div>
</div>
<div class="form-group">
<label for="answer">Answer</label>
<textarea name="answer" id="answer" class="form-control" rows="3" placeholder="Enter the answer..." required></textarea>
</div>
<button type="submit" class="btn-add" style="background: #212529; color: white; border: none; padding: 0.8rem 1.5rem; border-radius: 12px; cursor: pointer; font-weight: 600; width: 100%; transition: all 0.3s ease;">Save FAQ</button>
</form>
</div>
</div>
<h3>Existing FAQs</h3>
<table class="table">
<thead>
<tr>
<th>Keywords</th>
<th>Answer</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($faqs as $faq): ?>
<tr>
<td><?= htmlspecialchars($faq['keywords']) ?></td>
<td><?= htmlspecialchars($faq['answer']) ?></td>
<td>
<form method="POST" style="display:inline;" onsubmit="return confirm('Delete this FAQ?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $faq['id'] ?>">
<button type="submit" class="btn-delete">Delete</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h3 style="margin-top: 3rem; margin-bottom: 1rem;">Recent Chat History (Last 50)</h3>
<div style="overflow-x: auto; background: rgba(255, 255, 255, 0.4); padding: 1rem; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.3);">
<table class="table" style="width: 100%;">
<thead>
<tr>
<th style="width: 15%;">Time</th>
<th style="width: 35%;">User Message</th>
<th style="width: 50%;">AI Response</th>
</tr>
</thead>
<tbody>
<?php if (empty($messages)): ?>
<tr>
<td colspan="3" style="text-align: center; color: #777;">No messages yet.</td>
</tr>
<?php else: ?>
<?php foreach ($messages as $msg): ?>
<tr>
<td style="white-space: nowrap; font-size: 0.85em; color: #555;"><?= htmlspecialchars($msg['created_at']) ?></td>
<td style="background: rgba(255, 255, 255, 0.3); border-radius: 8px; padding: 8px;"><?= htmlspecialchars($msg['user_message']) ?></td>
<td style="background: rgba(255, 255, 255, 0.5); border-radius: 8px; padding: 8px;"><?= htmlspecialchars($msg['ai_response']) ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<div class="col-md-3">
<div class="card p-3">
<div class="d-flex align-items-center">
<div class="bg-success text-white p-3 rounded-4 me-3">
<i class="bi bi-check-circle fs-3"></i>
</div>
<div>
<div class="text-muted small"><?= __('ready_orders') ?? 'Ready Orders' ?></div>
<div class="fw-bold fs-5"><?= $stats['ready_orders'] ?></div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card p-3">
<div class="d-flex align-items-center">
<div class="bg-info text-white p-3 rounded-4 me-3">
<i class="bi bi-people fs-3"></i>
</div>
<div>
<div class="text-muted small"><?= __('new_customers') ?? 'New Customers' ?></div>
<div class="fw-bold fs-5"><?= $stats['new_customers'] ?></div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<div class="row">
<div class="col-md-8">
<div class="card p-4 h-100">
<h5 class="fw-bold mb-4"><?= __('recent_orders') ?? 'Recent Orders' ?></h5>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>#</th>
<th><?= __('customer_name') ?></th>
<th><?= __('total') ?></th>
<th><?= __('status') ?></th>
<th><?= __('payment_status') ?></th>
<th><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($recent_orders as $order): ?>
<tr>
<td><?= $order['id'] ?></td>
<td><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></td>
<td><?= number_format($order['total_price'], 2) ?></td>
<td><span class="badge bg-<?= getStatusColor($order['status']) ?>"><?= __($order['status']) ?></span></td>
<td><span class="badge bg-<?= getPaymentStatusColor($order['payment_status']) ?>"><?= __($order['payment_status']) ?></span></td>
<td>
<a href="order_details.php?id=<?= $order['id'] ?>" class="btn btn-sm btn-light border-0" style="border-radius: 8px;">
<i class="bi bi-eye"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card p-4 h-100 bg-primary text-white">
<h5 class="fw-bold mb-4"><?= __('quick_actions') ?? 'Quick Actions' ?></h5>
<a href="pos.php" class="btn btn-light w-100 mb-3 text-start py-3 px-4" style="border-radius: 15px; font-weight: 600;">
<i class="bi bi-plus-circle-fill me-2"></i> <?= __('new_order') ?>
</a>
<a href="customers.php" class="btn btn-light w-100 mb-3 text-start py-3 px-4" style="border-radius: 15px; font-weight: 600;">
<i class="bi bi-person-plus-fill me-2"></i> <?= __('add_new_customer') ?? 'Add New Customer' ?>
</a>
<a href="reports.php" class="btn btn-light w-100 text-start py-3 px-4" style="border-radius: 15px; font-weight: 600;">
<i class="bi bi-file-earmark-bar-graph-fill me-2"></i> <?= __('reports') ?? 'Reports' ?>
</a>
</div>
</div>
</div>
<?php
function getStatusColor($status) {
return [
'received' => 'secondary',
'processing' => 'primary',
'ready' => 'success',
'delivered' => 'dark',
'cancelled' => 'danger',
][$status] ?? 'info';
}
function getPaymentStatusColor($status) {
return [
'unpaid' => 'danger',
'partially_paid' => 'warning',
'paid' => 'success',
][$status] ?? 'info';
}
require_once __DIR__ . '/includes/footer.php';
?>

37
api/add_customer.php Normal file
View File

@ -0,0 +1,37 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
session_start();
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
$phone = $_POST['phone'] ?? '';
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$branch_id = $_SESSION['branch_id'];
if (!$phone || !$name_en) {
echo json_encode(['success' => false, 'error' => 'Phone and English Name are required']);
exit;
}
try {
$stmt = db()->prepare("INSERT INTO customers (branch_id, phone, name_en, name_ar) VALUES (?, ?, ?, ?)");
$stmt->execute([$branch_id, $phone, $name_en, $name_ar]);
$customer_id = db()->lastInsertId();
echo json_encode([
'success' => true,
'customer' => [
'id' => $customer_id,
'phone' => $phone,
'name_en' => $name_en,
'name_ar' => $name_ar
]
]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

View File

@ -0,0 +1,24 @@
<?php
require_once __DIR__ . '/../db/config.php';
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$phone = $_POST['phone'] ?? '';
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$email = $_POST['email'] ?? '';
$branch_id = $_SESSION['branch_id'];
if ($phone && $name_en) {
$stmt = db()->prepare("INSERT INTO customers (branch_id, phone, name_en, name_ar, email) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$branch_id, $phone, $name_en, $name_ar, $email]);
}
}
header('Location: ../customers.php');
exit;

59
api/checkout.php Normal file
View File

@ -0,0 +1,59 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
session_start();
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
$customer_id = $input['customer_id'] ?: null;
$items = $input['items'] ?? [];
$vat_total = (float)($input['vat_total'] ?? 0);
$total_price = (float)($input['total_price'] ?? 0);
$branch_id = $_SESSION['branch_id'];
$user_id = $_SESSION['user_id'];
if (empty($items)) {
echo json_encode(['success' => false, 'error' => 'Cart is empty']);
exit;
}
try {
$pdo = db();
$pdo->beginTransaction();
// Recalculate total if not provided correctly, but for now we trust the client-side breakdown
// If we wanted to be more secure, we'd fetch prices from DB here.
$order_number = 'ORD-' . time() . '-' . rand(100, 999);
$stmt = $pdo->prepare("INSERT INTO orders (branch_id, customer_id, user_id, order_number, total_price, vat_total, status, payment_status)
VALUES (?, ?, ?, ?, ?, ?, 'received', 'unpaid')");
$stmt->execute([$branch_id, $customer_id, $user_id, $order_number, $total_price, $vat_total]);
$order_id = $pdo->lastInsertId();
$stmt_item = $pdo->prepare("INSERT INTO order_items (order_id, item_id, variant_id, service_id, quantity, unit_price, vat_amount, subtotal)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
foreach ($items as $item) {
$subtotal = ($item['price'] * $item['quantity']);
$stmt_item->execute([
$order_id,
$item['itemId'],
$item['variantId'] ?: null,
$item['serviceId'],
$item['quantity'],
$item['price'],
$item['vatAmount'] ?: 0,
$subtotal
]);
}
$pdo->commit();
echo json_encode(['success' => true, 'order_id' => $order_id]);
} catch (Exception $e) {
if (isset($pdo)) $pdo->rollBack();
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

View File

@ -1,302 +1,84 @@
:root {
--bs-primary: #3b82f6;
--bs-primary-rgb: 59, 130, 246;
--bs-success: #10b981;
--bs-warning: #f59e0b;
--bs-danger: #ef4444;
}
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;
background-color: #f3f4f6;
}
.main-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
width: 100%;
padding: 20px;
box-sizing: border-box;
position: relative;
z-index: 1;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.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;
}
.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;
}
.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;
.card {
border-radius: 1.25rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.025);
border: none;
padding: 0.75rem 1.5rem;
border-radius: 12px;
cursor: pointer;
}
.btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
border-radius: 0.75rem;
}
.btn-primary:hover {
background-color: #2563eb;
border-color: #2563eb;
}
.sidebar {
background: #111827 !important;
}
.sidebar .nav-link {
font-weight: 500;
transition: all 0.2s;
}
.sidebar .nav-link:hover {
background: rgba(255, 255, 255, 0.05);
}
.sidebar .nav-link.active {
background: var(--bs-primary) !important;
}
.badge {
font-weight: 600;
transition: all 0.3s ease;
padding: 0.5em 1em;
}
.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); }
}
.admin-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;
}
.admin-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;
}
.table th {
background: transparent;
border: none;
padding: 1rem;
color: #6c757d;
font-weight: 600;
.table thead th {
background-color: #f9fafb;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 1px;
letter-spacing: 0.05em;
color: #6b7280;
border-bottom-width: 1px;
}
.table td {
background: #fff;
padding: 1rem;
border: none;
.form-control, .form-select {
border-radius: 0.75rem;
padding: 0.625rem 1rem;
border: 1px solid #d1d5db;
background-color: #fff;
}
.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;
.form-control:focus, .form-select:focus {
border-color: var(--bs-primary);
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
/* RTL Support for Cairo Font */
[dir="rtl"] {
font-family: 'Cairo', sans-serif !important;
}
.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;
.cursor-pointer {
cursor: pointer;
}
.form-control:focus {
outline: none;
border-color: #23a6d5;
box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
}
.item-card:hover {
transform: scale(1.02);
}

96
branches.php Normal file
View File

@ -0,0 +1,96 @@
<?php
// ACTION HANDLING FIRST
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$current_role = $_SESSION['role'] ?? 'cashier';
if ($current_role !== 'super_admin') {
header('Location: admin.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add_branch') {
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$company_id = $_POST['company_id'];
$phone = $_POST['phone'];
$stmt = db()->prepare("INSERT INTO branches (name_en, name_ar, company_id, phone) VALUES (?, ?, ?, ?)");
$stmt->execute([$name_en, $name_ar, $company_id, $phone]);
header('Location: branches.php');
exit;
}
}
// NOW Include header
$title = 'branches';
require_once __DIR__ . '/includes/header.php';
$branches = db()->query("SELECT b.*, c.name_en as company_name_en FROM branches b JOIN companies c ON b.company_id = c.id")->fetchAll();
$companies = db()->query("SELECT * FROM companies")->fetchAll();
?>
<div class="row">
<div class="col-md-4">
<div class="card p-4 shadow-sm border-0" style="border-radius: 20px;">
<h5 class="fw-bold mb-4"><?= __('add_new_branch') ?? 'Add New Branch' ?></h5>
<form method="POST">
<input type="hidden" name="action" value="add_branch">
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('company') ?? 'Company' ?></label>
<select name="company_id" class="form-select" style="border-radius: 12px;">
<?php foreach($companies as $c): ?>
<option value="<?= $c['id'] ?>"><?= $lang === 'ar' ? $c['name_ar'] : $c['name_en'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('name_en') ?></label>
<input type="text" name="name_en" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('name_ar') ?></label>
<input type="text" name="name_ar" class="form-control" style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('phone') ?></label>
<input type="text" name="phone" class="form-control" style="border-radius: 12px;">
</div>
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold" style="border-radius: 15px;"><?= __('add_branch') ?></button>
</form>
</div>
</div>
<div class="col-md-8">
<div class="card p-0 shadow-sm border-0" style="border-radius: 20px; overflow: hidden;">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr class="py-3">
<th class="ps-4 py-3">#</th>
<th class="py-3"><?= __('name') ?></th>
<th class="py-3"><?= __('company') ?></th>
<th class="pe-4 py-3"><?= __('phone') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($branches as $b): ?>
<tr>
<td class="ps-4"><?= $b['id'] ?></td>
<td class="fw-bold"><?= $lang === 'ar' ? ($b['name_ar'] ?: $b['name_en']) : $b['name_en'] ?></td>
<td><?= $b['company_name_en'] ?></td>
<td class="pe-4"><?= $b['phone'] ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

110
customers.php Normal file
View File

@ -0,0 +1,110 @@
<?php
$title = 'customers';
require_once __DIR__ . '/includes/header.php';
$branch_id = $_SESSION['branch_id'];
$search = $_GET['search'] ?? '';
$sql = "SELECT * FROM customers WHERE branch_id = ?";
$params = [$branch_id];
if ($search) {
$sql .= " AND (name_en LIKE ? OR name_ar LIKE ? OR phone LIKE ?)";
$params[] = "%$search%";
$params[] = "%$search%";
$params[] = "%$search%";
}
$sql .= " ORDER BY created_at DESC";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$customers = $stmt->fetchAll();
?>
<div class="card p-4">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
<h5 class="fw-bold mb-3 mb-md-0"><?= __('customers_list') ?? 'Customers List' ?></h5>
<div class="d-flex gap-2">
<form action="" method="GET" class="d-flex gap-2">
<div class="input-group">
<input type="text" name="search" class="form-control" placeholder="<?= __('search') ?>" value="<?= htmlspecialchars($search) ?>" style="border-radius: 12px 0 0 12px;">
<button class="btn btn-outline-secondary" type="submit" style="border-radius: 0 12px 12px 0;">
<i class="bi bi-search"></i>
</button>
</div>
</form>
<button class="btn btn-primary px-4" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-person-plus-fill me-1"></i> <?= __('add_new') ?>
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>#</th>
<th><?= __('name') ?></th>
<th><?= __('phone') ?></th>
<th><?= __('email') ?></th>
<th><?= __('date') ?></th>
<th><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($customers as $c): ?>
<tr>
<td><?= $c['id'] ?></td>
<td>
<div class="fw-bold"><?= $lang === 'ar' ? ($c['name_ar'] ?: $c['name_en']) : $c['name_en'] ?></div>
</td>
<td><?= $c['phone'] ?></td>
<td><?= $c['email'] ?: '-' ?></td>
<td class="small"><?= date('d/m/Y', strtotime($c['created_at'])) ?></td>
<td>
<button class="btn btn-sm btn-light border-0 p-2" style="border-radius: 8px;">
<i class="bi bi-pencil-fill"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Add Customer Modal -->
<div class="modal fade" id="addCustomerModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0" style="border-radius: 20px;">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold"><?= __('add_new_customer') ?? 'Add New Customer' ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form action="api/add_customer_redirect.php" method="POST">
<div class="mb-3">
<label class="form-label"><?= __('phone') ?></label>
<input type="text" name="phone" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label"><?= __('name_en') ?? 'Name (English)' ?></label>
<input type="text" name="name_en" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label"><?= __('name_ar') ?? 'Name (Arabic)' ?></label>
<input type="text" name="name_ar" class="form-control" style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label"><?= __('email') ?></label>
<input type="email" name="email" class="form-control" style="border-radius: 12px;">
</div>
<button type="submit" class="btn btn-primary w-100 py-3 mt-2 fw-bold" style="border-radius: 15px;"><?= __('save') ?></button>
</form>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -0,0 +1,129 @@
CREATE TABLE IF NOT EXISTS companies (
id INT AUTO_INCREMENT PRIMARY KEY,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS branches (
id INT AUTO_INCREMENT PRIMARY KEY,
company_id INT NOT NULL,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) NOT NULL,
address_en TEXT,
address_ar TEXT,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
branch_id INT,
company_id INT,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
full_name_en VARCHAR(255),
full_name_ar VARCHAR(255),
role ENUM('super_admin', 'branch_manager', 'cashier') DEFAULT 'cashier',
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE SET NULL,
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS customers (
id INT AUTO_INCREMENT PRIMARY KEY,
branch_id INT,
name_en VARCHAR(255),
name_ar VARCHAR(255),
phone VARCHAR(20) NOT NULL,
email VARCHAR(100),
address_en TEXT,
address_ar TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS items (
id INT AUTO_INCREMENT PRIMARY KEY,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS services (
id INT AUTO_INCREMENT PRIMARY KEY,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS prices (
id INT AUTO_INCREMENT PRIMARY KEY,
branch_id INT NOT NULL,
item_id INT NOT NULL,
service_id INT NOT NULL,
price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY (branch_id, item_id, service_id),
FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY,
branch_id INT NOT NULL,
customer_id INT,
user_id INT,
order_number VARCHAR(50) UNIQUE,
status ENUM('received', 'processing', 'ready', 'delivered', 'cancelled') DEFAULT 'received',
total_price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
payment_status ENUM('unpaid', 'partially_paid', 'paid') DEFAULT 'unpaid',
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (branch_id) REFERENCES branches(id),
FOREIGN KEY (customer_id) REFERENCES customers(id),
FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS order_items (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
item_id INT NOT NULL,
service_id INT NOT NULL,
quantity INT DEFAULT 1,
unit_price DECIMAL(10, 2) NOT NULL,
subtotal DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (item_id) REFERENCES items(id),
FOREIGN KEY (service_id) REFERENCES services(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS payments (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
payment_method ENUM('cash', 'card', 'transfer') DEFAULT 'cash',
transaction_id VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Initial data seed
INSERT INTO companies (name_en, name_ar) VALUES ('Laundry Brand', 'علامة غسيل');
INSERT INTO branches (company_id, name_en, name_ar) VALUES (1, 'Main Branch', 'الفرع الرئيسي');
INSERT INTO users (branch_id, company_id, username, password_hash, full_name_en, full_name_ar, role)
VALUES (1, 1, 'admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Admin', 'مدير', 'super_admin');
-- password is 'password'
INSERT INTO items (name_en, name_ar) VALUES ('Shirt', 'قميص'), ('Suit', 'بدلة'), ('T-Shirt', 'تيشيرت'), ('Pants', 'بنطال'), ('Dress', 'فستان');
INSERT INTO services (name_en, name_ar) VALUES ('Wash Only', 'غسيل فقط'), ('Iron Only', 'كوي فقط'), ('Wash & Iron', 'غسيل وكوي'), ('Dry Clean', 'تنظيف جاف');
-- Default prices for Main Branch
INSERT INTO prices (branch_id, item_id, service_id, price) VALUES
(1, 1, 1, 5.00), (1, 1, 2, 3.00), (1, 1, 3, 7.00),
(1, 2, 4, 30.00),
(1, 3, 3, 5.00),
(1, 4, 3, 6.00);

View File

@ -0,0 +1,37 @@
-- Create categories table
CREATE TABLE IF NOT EXISTS categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) NOT NULL,
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Add new columns to items table
ALTER TABLE items ADD COLUMN category_id INT AFTER id;
ALTER TABLE items ADD COLUMN image_url VARCHAR(255) AFTER name_ar;
ALTER TABLE items ADD COLUMN vat_percent DECIMAL(5, 2) DEFAULT 0.00 AFTER image_url;
-- Add foreign key for category
ALTER TABLE items ADD CONSTRAINT fk_item_category FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL;
-- Create item_variants table (interpreting "aspire table" as "a separate table for variants")
CREATE TABLE IF NOT EXISTS item_variants (
id INT AUTO_INCREMENT PRIMARY KEY,
item_id INT NOT NULL,
name_en VARCHAR(255) NOT NULL,
name_ar VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Update prices table to include variant_id (optional, making it more flexible)
ALTER TABLE prices ADD COLUMN variant_id INT AFTER item_id;
ALTER TABLE prices ADD CONSTRAINT fk_price_variant FOREIGN KEY (variant_id) REFERENCES item_variants(id) ON DELETE CASCADE;
-- Insert default categories
INSERT INTO categories (name_en, name_ar) VALUES
('Men', 'رجال'),
('Women', 'نساء'),
('Kids', 'أطفال'),
('Bed Sheets', 'ملاءات السرير');

View File

@ -0,0 +1,7 @@
-- Update orders table for VAT
ALTER TABLE orders ADD COLUMN vat_total DECIMAL(10, 2) DEFAULT 0.00 AFTER total_price;
-- Update order_items table for variants and VAT
ALTER TABLE order_items ADD COLUMN variant_id INT AFTER item_id;
ALTER TABLE order_items ADD COLUMN vat_amount DECIMAL(10, 2) DEFAULT 0.00 AFTER unit_price;
ALTER TABLE order_items ADD CONSTRAINT fk_order_item_variant FOREIGN KEY (variant_id) REFERENCES item_variants(id) ON DELETE SET NULL;

View File

@ -0,0 +1,3 @@
-- Fix prices table unique key to include variant_id
ALTER TABLE prices DROP INDEX branch_id;
ALTER TABLE prices ADD UNIQUE KEY unique_price (branch_id, item_id, variant_id, service_id);

8
includes/footer.php Normal file
View File

@ -0,0 +1,8 @@
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

158
includes/header.php Normal file
View File

@ -0,0 +1,158 @@
<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/lang.php';
// Authentication check (simple for now)
if (!isset($_SESSION['user_id']) && basename($_SERVER['PHP_SELF']) !== 'login.php') {
header('Location: login.php');
exit;
}
$current_user = $_SESSION['user_id'] ?? null;
$current_branch = $_SESSION['branch_id'] ?? null;
$current_role = $_SESSION['role'] ?? 'cashier';
?>
<!doctype html>
<html lang="<?= $lang ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laundry System - <?= __($title ?? 'dashboard') ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.<?= is_rtl() ? 'rtl.' : '' ?>min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<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;600;700&family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', <?= is_rtl() ? "'Cairo'," : '' ?> sans-serif;
background-color: #f8f9fa;
}
.sidebar {
min-height: 100vh;
background: #212529;
color: #fff;
padding-top: 1rem;
}
.sidebar .nav-link {
color: rgba(255,255,255,.75);
margin-bottom: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 12px;
display: flex;
align-items: center;
}
.sidebar .nav-link i {
margin-right: 0.75rem;
<?= is_rtl() ? 'margin-left: 0.75rem; margin-right: 0;' : '' ?>
font-size: 1.25rem;
}
.sidebar .nav-link:hover, .sidebar .nav-link.active {
color: #fff;
background: rgba(255,255,255,.1);
}
.sidebar .nav-link.active {
background: var(--bs-primary);
}
.main-content {
padding: 2rem;
}
.card {
border: none;
border-radius: 15px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
.lang-switch {
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
<div class="position-sticky">
<div class="px-4 mb-4 mt-2">
<h5 class="fw-bold">Laundry Admin</h5>
</div>
<ul class="nav flex-column px-3">
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'admin.php' ? 'active' : '' ?>" href="admin.php">
<i class="bi bi-speedometer2"></i>
<?= __('dashboard') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'pos.php' ? 'active' : '' ?>" href="pos.php">
<i class="bi bi-pc-display-horizontal"></i>
<?= __('pos') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'orders.php' ? 'active' : '' ?>" href="orders.php">
<i class="bi bi-cart"></i>
<?= __('orders') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'customers.php' ? 'active' : '' ?>" href="customers.php">
<i class="bi bi-people"></i>
<?= __('customers') ?>
</a>
</li>
<?php if ($current_role !== 'cashier'): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'items.php' ? 'active' : '' ?>" href="items.php">
<i class="bi bi-tags"></i>
<?= __('items') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'branches.php' ? 'active' : '' ?>" href="branches.php">
<i class="bi bi-shop"></i>
<?= __('branches') ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'users.php' ? 'active' : '' ?>" href="users.php">
<i class="bi bi-person-badge"></i>
<?= __('users') ?>
</a>
</li>
<?php endif; ?>
</ul>
<hr class="mx-3 my-4">
<div class="px-3">
<div class="nav-link text-white-50 small">
<?= __('language') ?>:
<a href="?lang=en" class="text-white ms-2">EN</a>
<span class="mx-1">|</span>
<a href="?lang=ar" class="text-white">عربي</a>
</div>
<a href="logout.php" class="nav-link text-danger mt-3">
<i class="bi bi-box-arrow-right"></i>
<?= __('logout') ?>
</a>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<header class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2"><?= __($title ?? 'dashboard') ?></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="me-2">
<span class="badge bg-primary px-3 py-2"><?= $_SESSION['branch_name'] ?? '' ?></span>
<span class="badge bg-secondary px-3 py-2 ms-2"><?= $_SESSION['full_name'] ?? '' ?></span>
</div>
</div>
</header>

200
includes/lang.php Normal file
View File

@ -0,0 +1,200 @@
<?php
session_start();
$lang = $_SESSION['lang'] ?? 'en';
if (isset($_GET['lang'])) {
$lang = $_GET['lang'] === 'ar' ? 'ar' : 'en';
$_SESSION['lang'] = $lang;
}
$translations = [
'en' => [
'dashboard' => 'Dashboard',
'pos' => 'POS',
'orders' => 'Orders',
'customers' => 'Customers',
'items' => 'Items',
'services' => 'Services',
'branches' => 'Branches',
'users' => 'Users',
'settings' => 'Settings',
'logout' => 'Logout',
'login' => 'Login',
'username' => 'Username',
'password' => 'Password',
'submit' => 'Submit',
'save' => 'Save',
'save_changes' => 'Save Changes',
'cancel' => 'Cancel',
'add_new' => 'Add New',
'edit' => 'Edit',
'delete' => 'Delete',
'actions' => 'Actions',
'search' => 'Search',
'total' => 'Total',
'status' => 'Status',
'date' => 'Date',
'phone' => 'Phone',
'name' => 'Name',
'name_en' => 'Name (EN)',
'name_ar' => 'Name (AR)',
'price' => 'Price',
'quantity' => 'Quantity',
'subtotal' => 'Subtotal',
'vat' => 'VAT',
'vat_total' => 'VAT Total',
'payment_status' => 'Payment Status',
'received' => 'Received',
'processing' => 'Processing',
'ready' => 'Ready',
'delivered' => 'Delivered',
'cancelled' => 'Cancelled',
'unpaid' => 'Unpaid',
'partially_paid' => 'Partially Paid',
'paid' => 'Paid',
'cash' => 'Cash',
'card' => 'Card',
'transfer' => 'Transfer',
'new_order' => 'New Order',
'order_details' => 'Order Details',
'customer_name' => 'Customer Name',
'select_customer' => 'Select Customer',
'add_item' => 'Add Item',
'edit_item' => 'Edit Item',
'delete_item' => 'Delete Item',
'select_item' => 'Select Item',
'select_service' => 'Select Service',
'checkout' => 'Checkout',
'back_to_pos' => 'Back to POS',
'language' => 'Language',
'arabic' => 'Arabic',
'english' => 'English',
'branch' => 'Branch',
'all_branches' => 'All Branches',
'category' => 'Category',
'categories' => 'Categories',
'add_category' => 'Add Category',
'edit_category' => 'Edit Category',
'delete_category' => 'Delete Category',
'categories_management' => 'Categories Management',
'add_new_category' => 'Add New Category',
'select_category' => 'Select Category',
'all' => 'All',
'variant' => 'Variant',
'variants' => 'Variants',
'add_variant' => 'Add Variant',
'select_variant' => 'Select Variant',
'variant_name_en' => 'Variant Name (EN)',
'variant_name_ar' => 'Variant Name (AR)',
'default' => 'Default',
'vat_percent' => 'VAT %',
'image_url' => 'Image URL',
'items_management' => 'Items Management',
'variants_prices' => 'Variants & Prices',
'no_items_found' => 'No items found',
'pricing_management' => 'Pricing Management',
'are_you_sure' => 'Are you sure?',
],
'ar' => [
'dashboard' => 'لوحة القيادة',
'pos' => 'نقطة البيع',
'orders' => 'الطلبات',
'customers' => 'العملاء',
'items' => 'الأصناف',
'services' => 'الخدمات',
'branches' => 'الفروع',
'users' => 'المستخدمين',
'settings' => 'الإعدادات',
'logout' => 'تسجيل الخروج',
'login' => 'تسجيل الدخول',
'username' => 'اسم المستخدم',
'password' => 'كلمة المرور',
'submit' => 'إرسال',
'save' => 'حفظ',
'save_changes' => 'حفظ التغييرات',
'cancel' => 'إلغاء',
'add_new' => 'إضافة جديد',
'edit' => 'تعديل',
'delete' => 'حذف',
'actions' => 'إجراءات',
'search' => 'بحث',
'total' => 'الإجمالي',
'status' => 'الحالة',
'date' => 'التاريخ',
'phone' => 'الهاتف',
'name' => 'الاسم',
'name_en' => 'الاسم (EN)',
'name_ar' => 'الاسم (AR)',
'price' => 'السعر',
'quantity' => 'الكمية',
'subtotal' => 'المجموع الفرعي',
'vat' => 'ضريبة القيمة المضافة',
'vat_total' => 'إجمالي الضريبة',
'payment_status' => 'حالة الدفع',
'received' => 'تم الاستلام',
'processing' => 'قيد المعالجة',
'ready' => 'جاهز',
'delivered' => 'تم التسليم',
'cancelled' => 'ملغي',
'unpaid' => 'غير مدفوع',
'partially_paid' => 'مدفوع جزئياً',
'paid' => 'مدفوع',
'cash' => 'نقد',
'card' => 'بطاقة',
'transfer' => 'تحويل',
'new_order' => 'طلب جديد',
'order_details' => 'تفاصيل الطلب',
'customer_name' => 'اسم العميل',
'select_customer' => 'اختر العميل',
'add_item' => 'إضافة صنف',
'edit_item' => 'تعديل صنف',
'delete_item' => 'حذف صنف',
'select_item' => 'اختر الصنف',
'select_service' => 'اختر الخدمة',
'checkout' => 'إتمام الطلب',
'back_to_pos' => 'العودة لنقطة البيع',
'language' => 'اللغة',
'arabic' => 'العربية',
'english' => 'الإنجليزية',
'branch' => 'الفرع',
'all_branches' => 'جميع الفروع',
'category' => 'الفئة',
'categories' => 'الفئات',
'add_category' => 'إضافة فئة',
'edit_category' => 'تعديل فئة',
'delete_category' => 'حذف فئة',
'categories_management' => 'إدارة الفئات',
'add_new_category' => 'إضافة فئة جديدة',
'select_category' => 'اختر الفئة',
'all' => 'الكل',
'variant' => 'النوع',
'variants' => 'الأنواع',
'add_variant' => 'إضافة نوع',
'select_variant' => 'اختر النوع',
'variant_name_en' => 'اسم النوع (EN)',
'variant_name_ar' => 'اسم النوع (AR)',
'default' => 'افتراضي',
'vat_percent' => 'نسبة الضريبة %',
'image_url' => 'رابط الصورة',
'items_management' => 'إدارة الأصناف',
'variants_prices' => 'الأنواع والأسعار',
'no_items_found' => 'لا يوجد أصناف',
'pricing_management' => 'إدارة الأسعار',
'are_you_sure' => 'هل أنت متأكد؟',
]
];
function __($key) {
global $translations, $lang;
return $translations[$lang][$key] ?? $key;
}
function is_rtl() {
global $lang;
return $lang === 'ar';
}
function is_arabic() {
global $lang;
return $lang === 'ar';
}

View File

@ -1,52 +1,8 @@
<?php
require_once __DIR__ . '/db/config.php';
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Modern AI-ready Chat Assistant';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Chat Assistant</title>
<?php if ($projectDescription): ?>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>">
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>">
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<meta property="og: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;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<div class="bg-animations">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
</div>
<div class="main-wrapper">
<div class="chat-container">
<div class="chat-header">
<span>Chat Assistant</span>
<a href="admin.php" class="admin-link">Admin</a>
</div>
<div class="chat-messages" id="chat-messages">
<div class="message bot">
Hello! I'm your assistant. How can I help you today?
</div>
</div>
<div class="chat-input-area">
<form id="chat-form">
<input type="text" id="chat-input" placeholder="Type your message..." autocomplete="off">
<button type="submit">Send</button>
</form>
</div>
</div>
</div>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>
session_start();
if (isset($_SESSION['user_id'])) {
header('Location: admin.php');
} else {
header('Location: login.php');
}
exit;

860
items.php Normal file
View File

@ -0,0 +1,860 @@
<?php
// ACTION HANDLING FIRST (to allow redirects)
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.php';
if (!isset($_SESSION['user_id'])) {
if (isset($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
header('Location: login.php');
exit;
}
$current_role = $_SESSION['role'] ?? 'cashier';
if ($current_role === 'cashier') {
if (isset($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Forbidden']);
exit;
}
header('Location: admin.php');
exit;
}
$branch_id = $_SESSION['branch_id'];
function handleImageUpload($file) {
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
return null;
}
$targetDir = "assets/images/items/";
if (!is_dir($targetDir)) {
mkdir($targetDir, 0775, true);
}
$fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$newFileName = uniqid('item_', true) . '.' . $fileExtension;
$targetFile = $targetDir . $newFileName;
// Check if image file is a actual image or fake image
$check = getimagesize($file['tmp_name']);
if($check === false) {
return null;
}
// Allow certain file formats
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (!in_array($fileExtension, $allowedExtensions)) {
return null;
}
if (move_uploaded_file($file['tmp_name'], $targetFile)) {
return $targetFile;
}
return null;
}
function deleteOldImage($imageUrl) {
if ($imageUrl && strpos($imageUrl, 'assets/images/items/') === 0) {
if (file_exists($imageUrl)) {
unlink($imageUrl);
}
}
}
// Handle Actions
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add_item') {
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$category_id = $_POST['category_id'] ?: null;
$vat_percent = $_POST['vat_percent'] ?: 0.00;
$image_url = handleImageUpload($_FILES['image'] ?? null);
$stmt = db()->prepare("INSERT INTO items (category_id, name_en, name_ar, image_url, vat_percent) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$category_id, $name_en, $name_ar, $image_url, $vat_percent]);
header('Location: items.php?success=item_added');
exit;
} elseif ($_POST['action'] === 'edit_item') {
$id = $_POST['id'];
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$category_id = $_POST['category_id'] ?: null;
$vat_percent = $_POST['vat_percent'] ?: 0.00;
// Get current image to delete it if new one is uploaded
$stmt = db()->prepare("SELECT image_url FROM items WHERE id = ?");
$stmt->execute([$id]);
$currentItem = $stmt->fetch();
$image_url = $currentItem['image_url'];
$new_image = handleImageUpload($_FILES['image'] ?? null);
if ($new_image) {
deleteOldImage($image_url);
$image_url = $new_image;
}
$stmt = db()->prepare("UPDATE items SET category_id = ?, name_en = ?, name_ar = ?, image_url = ?, vat_percent = ? WHERE id = ?");
$stmt->execute([$category_id, $name_en, $name_ar, $image_url, $vat_percent, $id]);
header('Location: items.php?success=item_updated');
exit;
} elseif ($_POST['action'] === 'delete_item') {
$id = $_POST['id'];
// Delete image file if exists
$stmt = db()->prepare("SELECT image_url FROM items WHERE id = ?");
$stmt->execute([$id]);
$item = $stmt->fetch();
deleteOldImage($item['image_url']);
$stmt = db()->prepare("DELETE FROM items WHERE id = ?");
$stmt->execute([$id]);
header('Location: items.php?success=item_deleted');
exit;
} elseif ($_POST['action'] === 'add_category') {
$name_en = $_POST['cat_name_en'];
$name_ar = $_POST['cat_name_ar'];
$stmt = db()->prepare("INSERT INTO categories (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$name_en, $name_ar]);
header('Location: items.php?success=category_added');
exit;
} elseif ($_POST['action'] === 'edit_category') {
$id = $_POST['id'];
$name_en = $_POST['cat_name_en'];
$name_ar = $_POST['cat_name_ar'];
$stmt = db()->prepare("UPDATE categories SET name_en = ?, name_ar = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $id]);
header('Location: items.php?success=category_updated');
exit;
} elseif ($_POST['action'] === 'delete_category') {
$id = $_POST['id'];
$stmt = db()->prepare("DELETE FROM categories WHERE id = ?");
$stmt->execute([$id]);
header('Location: items.php?success=category_deleted');
exit;
} elseif ($_POST['action'] === 'add_service') {
$name_en = $_POST['svc_name_en'];
$name_ar = $_POST['svc_name_ar'];
$stmt = db()->prepare("INSERT INTO services (name_en, name_ar) VALUES (?, ?)");
$stmt->execute([$name_en, $name_ar]);
header('Location: items.php?success=service_added');
exit;
} elseif ($_POST['action'] === 'edit_service') {
$id = $_POST['id'];
$name_en = $_POST['svc_name_en'];
$name_ar = $_POST['svc_name_ar'];
$stmt = db()->prepare("UPDATE services SET name_en = ?, name_ar = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $id]);
header('Location: items.php?success=service_updated');
exit;
} elseif ($_POST['action'] === 'delete_service') {
$id = $_POST['id'];
$stmt = db()->prepare("DELETE FROM services WHERE id = ?");
$stmt->execute([$id]);
header('Location: items.php?success=service_deleted');
exit;
} elseif ($_POST['action'] === 'update_price') {
$item_id = $_POST['item_id'];
$service_id = $_POST['service_id'];
$variant_id = $_POST['variant_id'] ?: null;
$price = $_POST['price'];
$stmt = db()->prepare("INSERT INTO prices (branch_id, item_id, variant_id, service_id, price)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE price = ?");
$stmt->execute([$branch_id, $item_id, $variant_id, $service_id, $price, $price]);
if (isset($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['success' => true]);
exit;
}
header('Location: items.php?success=price_updated');
exit;
} elseif ($_POST['action'] === 'add_variant') {
$item_id = $_POST['item_id'];
$name_en = $_POST['variant_name_en'];
$name_ar = $_POST['variant_name_ar'];
$stmt = db()->prepare("INSERT INTO item_variants (item_id, name_en, name_ar) VALUES (?, ?, ?)");
$stmt->execute([$item_id, $name_en, $name_ar]);
header('Location: items.php?success=variant_added');
exit;
} elseif ($_POST['action'] === 'edit_variant') {
$id = $_POST['id'];
$name_en = $_POST['name_en'];
$name_ar = $_POST['name_ar'];
$stmt = db()->prepare("UPDATE item_variants SET name_en = ?, name_ar = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $id]);
if (isset($_POST['ajax'])) {
header('Content-Type: application/json');
echo json_encode(['success' => true]);
exit;
}
header('Location: items.php?success=variant_updated');
exit;
} elseif ($_POST['action'] === 'delete_variant') {
$id = $_POST['id'];
$stmt = db()->prepare("DELETE FROM item_variants WHERE id = ?");
$stmt->execute([$id]);
header('Location: items.php?success=variant_deleted');
exit;
}
}
// NOW Include header
$title = 'items';
require_once __DIR__ . '/includes/header.php';
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll();
$items = db()->query("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar
FROM items i
LEFT JOIN categories c ON i.category_id = c.id
ORDER BY i.name_en ASC")->fetchAll();
$services = db()->query("SELECT * FROM services ORDER BY name_en ASC")->fetchAll();
$stmt = db()->prepare("SELECT * FROM prices WHERE branch_id = ?");
$stmt->execute([$branch_id]);
$prices_raw = $stmt->fetchAll();
$prices = [];
foreach ($prices_raw as $p) {
$v_key = $p['variant_id'] ?: 'default';
$prices[$p['item_id']][$v_key][$p['service_id']] = $p['price'];
}
$item_variants = db()->query("SELECT * FROM item_variants ORDER BY name_en ASC")->fetchAll();
$variants_by_item = [];
foreach ($item_variants as $v) {
$variants_by_item[$v['item_id']][] = $v;
}
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="fw-bold m-0"><?= __('items_management') ?? 'Items Management' ?></h4>
<div>
<button class="btn btn-outline-secondary px-4 fw-bold me-2" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#categoryModal">
<i class="bi bi-tag me-2"></i><?= __('categories') ?? 'Categories' ?>
</button>
<button class="btn btn-outline-primary px-4 fw-bold me-2" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#serviceModal">
<i class="bi bi-gear me-2"></i><?= __('services') ?? 'Services' ?>
</button>
<button class="btn btn-primary px-4 fw-bold" style="border-radius: 12px;" onclick="openItemModal()">
<i class="bi bi-plus-lg me-2"></i><?= __('add_item') ?>
</button>
</div>
</div>
<?php if(isset($_GET['success'])): ?>
<div class="alert alert-success alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
<i class="bi bi-check-circle-fill me-2"></i>
<?= __($_GET['success']) ?? 'Action completed successfully' ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="card p-0 border-0 shadow-sm mb-4" style="border-radius: 20px; overflow: hidden;">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4 py-3"><?= __('item') ?></th>
<th class="py-3"><?= __('category') ?></th>
<th class="py-3"><?= __('vat_percent') ?></th>
<th class="py-3"><?= __('variants_prices') ?? 'Pricing & Services' ?></th>
<th class="pe-4 py-3 text-end"><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<div class="bg-light rounded-3 me-3 d-flex align-items-center justify-content-center" style="width: 48px; height: 48px; overflow: hidden;">
<?php if($item['image_url']): ?>
<img src="<?= $item['image_url'] ?>?v=<?= time() ?>" alt="" style="width: 100%; height: 100%; object-fit: cover;">
<?php else: ?>
<i class="bi bi-image text-muted"></i>
<?php endif; ?>
</div>
<div>
<div class="fw-bold"><?= $lang === 'ar' ? ($item['name_ar'] ?: $item['name_en']) : $item['name_en'] ?></div>
<?php if($lang === 'ar' && $item['name_ar']): ?>
<small class="text-muted"><?= $item['name_en'] ?></small>
<?php elseif($lang === 'en' && $item['name_ar']): ?>
<small class="text-muted"><?= $item['name_ar'] ?></small>
<?php endif; ?>
</div>
</div>
</td>
<td>
<span class="badge bg-soft-primary text-primary px-3 py-2" style="border-radius: 8px;">
<?= $lang === 'ar' ? ($item['cat_ar'] ?: $item['cat_en']) : $item['cat_en'] ?>
</span>
</td>
<td><?= number_format($item['vat_percent'], 2) ?>%</td>
<td>
<button class="btn btn-sm btn-outline-info px-3" style="border-radius: 8px;" data-bs-toggle="modal" data-bs-target="#pricingModal<?= $item['id'] ?>">
<i class="bi bi-currency-dollar me-1"></i> <?= count($variants_by_item[$item['id']] ?? []) ?> <?= __('variants') ?> / <?= count($services) ?> <?= __('services') ?>
</button>
</td>
<td class="pe-4 text-end">
<button class="btn btn-sm btn-light p-2 me-1" style="border-radius: 10px;" onclick="openItemModal(<?= htmlspecialchars(json_encode($item)) ?>)">
<i class="bi bi-pencil text-primary"></i>
</button>
<button class="btn btn-sm btn-light p-2" style="border-radius: 10px;" onclick="confirmDelete('item', <?= $item['id'] ?>)">
<i class="bi bi-trash text-danger"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="alert alert-info border-0 shadow-sm rounded-4 mb-4">
<div class="d-flex">
<i class="bi bi-info-circle-fill me-3 fs-4"></i>
<div>
<h6 class="fw-bold mb-1">How to use Variants & Services?</h6>
<p class="mb-0 small">
<strong>Services:</strong> General actions like "Wash Only", "Urgent Wash", "Iron Only". Add them once and set prices for each item.<br>
<strong>Variants:</strong> Specific garment types or materials like "Silk Dress", "Wool Suit", or "Large Carpet". Use them if an item has different versions with unique pricing.
</p>
</div>
</div>
</div>
<!-- Item Modal (Add/Edit) -->
<div class="modal fade" id="itemModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 25px;">
<form id="itemForm" method="POST" enctype="multipart/form-data">
<div class="modal-header border-0 p-4 pb-0">
<h5 class="modal-title fw-bold" id="itemModalLabel"><?= __('add_item') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<input type="hidden" name="action" id="itemAction" value="add_item">
<input type="hidden" name="id" id="itemId">
<div class="mb-3">
<label class="form-label fw-bold small"><?= __('category') ?></label>
<select name="category_id" id="itemCategory" class="form-select" style="border-radius: 12px;" required>
<option value=""><?= __('select_category') ?></option>
<?php foreach($categories as $cat): ?>
<option value="<?= $cat['id'] ?>"><?= $lang === 'ar' ? ($cat['name_ar'] ?: $cat['name_en']) : $cat['name_en'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold small"><?= __('name_en') ?></label>
<input type="text" name="name_en" id="itemNameEn" class="form-control" required style="border-radius: 12px;">
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold small"><?= __('name_ar') ?></label>
<input type="text" name="name_ar" id="itemNameAr" class="form-control" style="border-radius: 12px;">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold small"><?= __('vat_percent') ?></label>
<div class="input-group">
<input type="number" step="0.01" name="vat_percent" id="itemVat" class="form-control" value="0.00" style="border-top-left-radius: 12px; border-bottom-left-radius: 12px;">
<span class="input-group-text" style="border-top-right-radius: 12px; border-bottom-right-radius: 12px;">%</span>
</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold small"><?= __('image') ?? 'Image' ?></label>
<input type="file" name="image" id="itemImageFile" class="form-control" style="border-radius: 12px;" accept="image/*">
<div id="imagePreviewContainer" class="mt-2 d-none">
<img id="imagePreview" src="" alt="Preview" class="img-thumbnail" style="max-height: 100px;">
</div>
</div>
</div>
</div>
<div class="modal-footer border-0 p-4 pt-0">
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold shadow-sm" style="border-radius: 15px;">
<?= __('save_changes') ?? 'Save Changes' ?>
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Categories Modal -->
<div class="modal fade" id="categoryModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content" style="border-radius: 25px;">
<div class="modal-header border-0 p-4">
<h5 class="modal-title fw-bold"><?= __('categories_management') ?? 'Categories Management' ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4 pt-0">
<div class="row">
<div class="col-md-5">
<div class="bg-light p-4" style="border-radius: 20px;">
<h6 class="fw-bold mb-3" id="catFormLabel"><?= __('add_new_category') ?></h6>
<form id="categoryForm" method="POST">
<input type="hidden" name="action" id="catAction" value="add_category">
<input type="hidden" name="id" id="catId">
<div class="mb-3">
<label class="form-label small"><?= __('name_en') ?></label>
<input type="text" name="cat_name_en" id="catNameEn" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small"><?= __('name_ar') ?></label>
<input type="text" name="cat_name_ar" id="catNameAr" class="form-control" style="border-radius: 12px;">
</div>
<button type="submit" class="btn btn-secondary w-100 py-2 fw-bold" style="border-radius: 12px;">
<?= __('save') ?>
</button>
<button type="button" id="cancelCatEdit" class="btn btn-link w-100 btn-sm mt-2 text-decoration-none d-none" onclick="resetCatForm()">
<?= __('cancel') ?>
</button>
</form>
</div>
</div>
<div class="col-md-7">
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-sm align-middle">
<thead>
<tr>
<th><?= __('name') ?></th>
<th class="text-end"><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($categories as $cat): ?>
<tr>
<td>
<strong><?= $cat['name_en'] ?></strong><br>
<small class="text-muted"><?= $cat['name_ar'] ?></small>
</td>
<td class="text-end">
<button class="btn btn-sm btn-link text-primary" onclick="editCategory(<?= htmlspecialchars(json_encode($cat)) ?>)">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-link text-danger" onclick="confirmDelete('category', <?= $cat['id'] ?>)">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Services Modal -->
<div class="modal fade" id="serviceModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content" style="border-radius: 25px;">
<div class="modal-header border-0 p-4">
<h5 class="modal-title fw-bold"><?= __('services_management') ?? 'Services Management' ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4 pt-0">
<div class="row">
<div class="col-md-5">
<div class="bg-light p-4" style="border-radius: 20px;">
<h6 class="fw-bold mb-3" id="svcFormLabel"><?= __('add_new_service') ?></h6>
<form id="serviceForm" method="POST">
<input type="hidden" name="action" id="svcAction" value="add_service">
<input type="hidden" name="id" id="svcId">
<div class="mb-3">
<label class="form-label small"><?= __('name_en') ?></label>
<input type="text" name="svc_name_en" id="svcNameEn" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small"><?= __('name_ar') ?></label>
<input type="text" name="svc_name_ar" id="svcNameAr" class="form-control" style="border-radius: 12px;">
</div>
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold" style="border-radius: 12px;">
<?= __('save') ?>
</button>
<button type="button" id="cancelSvcEdit" class="btn btn-link w-100 btn-sm mt-2 text-decoration-none d-none" onclick="resetSvcForm()">
<?= __('cancel') ?>
</button>
</form>
</div>
</div>
<div class="col-md-7">
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-sm align-middle">
<thead>
<tr>
<th><?= __('name') ?></th>
<th class="text-end"><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($services as $svc): ?>
<tr>
<td>
<strong><?= $svc['name_en'] ?></strong><br>
<small class="text-muted"><?= $svc['name_ar'] ?></small>
</td>
<td class="text-end">
<button class="btn btn-sm btn-link text-primary" onclick="editService(<?= htmlspecialchars(json_encode($svc)) ?>)">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-link text-danger" onclick="confirmDelete('service', <?= $svc['id'] ?>)">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Pricing Modals -->
<?php foreach($items as $item): ?>
<div class="modal fade pricing-modal" id="pricingModal<?= $item['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content" style="border-radius: 25px;">
<div class="modal-header border-0 p-4">
<div>
<h5 class="modal-title fw-bold m-0"><?= __('pricing_management') ?></h5>
<p class="text-muted m-0 small"><?= $lang === 'ar' ? $item['name_ar'] : $item['name_en'] ?></p>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4 pt-0">
<!-- Add Variant Form -->
<div class="bg-light p-3 mb-4 d-flex align-items-end" style="border-radius: 15px;">
<form method="POST" class="row g-2 w-100">
<input type="hidden" name="action" value="add_variant">
<input type="hidden" name="item_id" value="<?= $item['id'] ?>">
<div class="col-md-4">
<label class="form-label small fw-bold mb-1"><?= __('variant_name_en') ?? 'Variant Name (e.g. Silk, Long, Small)' ?></label>
<input type="text" name="variant_name_en" class="form-control form-control-sm" required style="border-radius: 8px;">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1"><?= __('variant_name_ar') ?? 'Variant Name (AR)' ?></label>
<input type="text" name="variant_name_ar" class="form-control form-control-sm" style="border-radius: 8px;">
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-sm btn-primary w-100 py-2 fw-bold" style="border-radius: 8px;">
<i class="bi bi-plus-lg me-1"></i> <?= __('add_variant') ?>
</button>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="bg-light">
<tr class="small text-muted">
<th style="min-width: 200px;"><?= __('variant') ?></th>
<?php foreach($services as $s): ?>
<th class="text-center"><?= $lang === 'ar' ? $s['name_ar'] : $s['name_en'] ?></th>
<?php endforeach; ?>
<th class="text-end"><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<!-- Default Variant -->
<tr>
<td class="fw-bold">
<div class="d-flex align-items-center">
<i class="bi bi-star-fill text-warning me-2"></i>
<?= __('default') ?>
</div>
</td>
<?php foreach($services as $service): ?>
<td class="text-center">
<div class="input-group input-group-sm justify-content-center mx-auto" style="width: 120px; position: relative;">
<input type="number" step="0.01" class="form-control text-center price-input"
value="<?= $prices[$item['id']]['default'][$service['id']] ?? 0.00 ?>"
data-item-id="<?= $item['id'] ?>"
data-variant-id=""
data-service-id="<?= $service['id'] ?>"
style="border-radius: 8px;">
<span class="input-group-text bg-transparent border-0 d-none success-indicator" style="position: absolute; right: -25px; top: 5px;">
<i class="bi bi-check-lg text-success"></i>
</span>
</div>
</td>
<?php endforeach; ?>
<td class="text-end"></td>
</tr>
<!-- Custom Variants -->
<?php if(isset($variants_by_item[$item['id']])): ?>
<?php foreach($variants_by_item[$item['id']] as $variant): ?>
<tr>
<td>
<div class="row g-1 position-relative">
<div class="col-6">
<input type="text" class="form-control form-control-sm variant-name-input"
value="<?= $variant['name_en'] ?>"
data-variant-id="<?= $variant['id'] ?>"
data-field="name_en"
placeholder="EN"
style="border-radius: 6px;">
</div>
<div class="col-6">
<input type="text" class="form-control form-control-sm variant-name-input"
value="<?= $variant['name_ar'] ?>"
data-variant-id="<?= $variant['id'] ?>"
data-field="name_ar"
placeholder="AR"
style="border-radius: 6px; text-align: right;">
</div>
<span class="success-indicator d-none" style="position: absolute; right: -20px; top: 8px;">
<i class="bi bi-check-lg text-success"></i>
</span>
</div>
</td>
<?php foreach($services as $service): ?>
<td class="text-center">
<div class="input-group input-group-sm justify-content-center mx-auto" style="width: 120px; position: relative;">
<input type="number" step="0.01" class="form-control text-center price-input"
value="<?= $prices[$item['id']][$variant['id']][$service['id']] ?? 0.00 ?>"
data-item-id="<?= $item['id'] ?>"
data-variant-id="<?= $variant['id'] ?>"
data-service-id="<?= $service['id'] ?>"
style="border-radius: 8px;">
<span class="input-group-text bg-transparent border-0 d-none success-indicator" style="position: absolute; right: -25px; top: 5px;">
<i class="bi bi-check-lg text-success"></i>
</span>
</div>
</td>
<?php endforeach; ?>
<td class="text-end">
<button type="button" class="btn btn-sm btn-light text-danger" onclick="confirmDeleteVariant(<?= $variant['id'] ?>)" style="border-radius: 8px;">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="alert alert-info py-2 small mt-3 mb-0" style="border-radius: 10px;">
<i class="bi bi-info-circle me-2"></i> Prices and variant names are saved automatically as you type.
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<!-- Delete Confirmation Form -->
<form id="deleteForm" method="POST" style="display: none;">
<input type="hidden" name="action" id="deleteAction">
<input type="hidden" name="id" id="deleteId">
</form>
<style>
.bg-soft-primary { background-color: rgba(13, 110, 253, 0.1); }
.price-input:focus, .variant-name-input:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.1);
}
.pricing-modal .table th { border-top: none; }
</style>
<script>
function openItemModal(item = null) {
const modal = new bootstrap.Modal(document.getElementById('itemModal'));
const form = document.getElementById('itemForm');
const label = document.getElementById('itemModalLabel');
const actionInput = document.getElementById('itemAction');
const idInput = document.getElementById('itemId');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const imagePreview = document.getElementById('imagePreview');
const imageInput = document.getElementById('itemImageFile');
// Reset file input and preview
imageInput.value = '';
imagePreview.src = '';
imagePreviewContainer.classList.add('d-none');
if (item) {
label.innerText = "<?= __('edit_item') ?>";
actionInput.value = 'edit_item';
idInput.value = item.id;
document.getElementById('itemNameEn').value = item.name_en;
document.getElementById('itemNameAr').value = item.name_ar;
document.getElementById('itemCategory').value = item.category_id;
document.getElementById('itemVat').value = item.vat_percent;
if (item.image_url) {
imagePreview.src = item.image_url + '?v=' + new Date().getTime();
imagePreviewContainer.classList.remove('d-none');
}
} else {
label.innerText = "<?= __('add_item') ?>";
actionInput.value = 'add_item';
form.reset();
idInput.value = '';
}
modal.show();
}
function editCategory(cat) {
document.getElementById('catFormLabel').innerText = "<?= __('edit_category') ?? 'Edit Category' ?>";
document.getElementById('catAction').value = 'edit_category';
document.getElementById('catId').value = cat.id;
document.getElementById('catNameEn').value = cat.name_en;
document.getElementById('catNameAr').value = cat.name_ar;
document.getElementById('cancelCatEdit').classList.remove('d-none');
}
function resetCatForm() {
document.getElementById('catFormLabel').innerText = "<?= __('add_new_category') ?>";
document.getElementById('catAction').value = 'add_category';
document.getElementById('categoryForm').reset();
document.getElementById('catId').value = '';
document.getElementById('cancelCatEdit').classList.add('d-none');
}
function editService(svc) {
document.getElementById('svcFormLabel').innerText = "<?= __('edit_service') ?? 'Edit Service' ?>";
document.getElementById('svcAction').value = 'edit_service';
document.getElementById('svcId').value = svc.id;
document.getElementById('svcNameEn').value = svc.name_en;
document.getElementById('svcNameAr').value = svc.name_ar;
document.getElementById('cancelSvcEdit').classList.remove('d-none');
}
function resetSvcForm() {
document.getElementById('svcFormLabel').innerText = "<?= __('add_new_service') ?>";
document.getElementById('svcAction').value = 'add_service';
document.getElementById('serviceForm').reset();
document.getElementById('svcId').value = '';
document.getElementById('cancelSvcEdit').classList.add('d-none');
}
function confirmDelete(type, id) {
if (confirm("<?= __('are_you_sure') ?>")) {
document.getElementById('deleteAction').value = 'delete_' + type;
document.getElementById('deleteId').value = id;
document.getElementById('deleteForm').submit();
}
}
function confirmDeleteVariant(id) {
if (confirm("<?= __('are_you_sure') ?>")) {
document.getElementById('deleteAction').value = 'delete_variant';
document.getElementById('deleteId').value = id;
document.getElementById('deleteForm').submit();
}
}
// AJAX logic for prices and variants
document.querySelectorAll('.price-input').forEach(input => {
let timeout = null;
input.addEventListener('input', function() {
clearTimeout(timeout);
const self = this;
timeout = setTimeout(async () => {
const formData = new FormData();
formData.append('action', 'update_price');
formData.append('ajax', '1');
formData.append('item_id', self.dataset.itemId);
formData.append('variant_id', self.dataset.variantId);
formData.append('service_id', self.dataset.serviceId);
formData.append('price', self.value);
try {
const response = await fetch('items.php', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showIndicator(self);
}
} catch (error) {
console.error('Error updating price:', error);
}
}, 500);
});
});
document.querySelectorAll('.variant-name-input').forEach(input => {
let timeout = null;
input.addEventListener('input', function() {
clearTimeout(timeout);
const self = this;
timeout = setTimeout(async () => {
const row = self.closest('tr');
const enInput = row.querySelector('[data-field="name_en"]');
const arInput = row.querySelector('[data-field="name_ar"]');
const formData = new FormData();
formData.append('action', 'edit_variant');
formData.append('ajax', '1');
formData.append('id', self.dataset.variantId);
formData.append('name_en', enInput.value);
formData.append('name_ar', arInput.value);
try {
const response = await fetch('items.php', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showIndicator(self);
}
} catch (error) {
console.error('Error updating variant:', error);
}
}, 500);
});
});
function showIndicator(input) {
const indicator = input.parentElement.querySelector('.success-indicator') || input.closest('td').querySelector('.success-indicator');
if (indicator) {
indicator.classList.remove('d-none');
setTimeout(() => {
indicator.classList.add('d-none');
}, 2000);
}
}
// Image preview logic
document.getElementById('itemImageFile').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('imagePreview').src = e.target.result;
document.getElementById('imagePreviewContainer').classList.remove('d-none');
}
reader.readAsDataURL(file);
}
});
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

86
login.php Normal file
View File

@ -0,0 +1,86 @@
<?php
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.php';
if (isset($_SESSION['user_id'])) {
header('Location: admin.php');
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$stmt = db()->prepare("SELECT u.*, b.name_en as branch_name_en, b.name_ar as branch_name_ar
FROM users u
LEFT JOIN branches b ON u.branch_id = b.id
WHERE u.username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['branch_id'] = $user['branch_id'];
$_SESSION['company_id'] = $user['company_id'];
$_SESSION['role'] = $user['role'];
$_SESSION['full_name'] = $lang === 'ar' ? ($user['full_name_ar'] ?: $user['full_name_en']) : $user['full_name_en'];
$_SESSION['branch_name'] = $lang === 'ar' ? ($user['branch_name_ar'] ?: $user['branch_name_en']) : $user['branch_name_en'];
header('Location: admin.php');
exit;
} else {
$error = 'Invalid username or password';
}
}
?>
<!doctype html>
<html lang="<?= $lang ?>" dir="<?= is_rtl() ? 'rtl' : 'ltr' ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= __('login') ?></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.<?= is_rtl() ? 'rtl.' : '' ?>min.css">
<style>
body {
background-color: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: 'Inter', sans-serif;
}
.login-card {
width: 100%;
max-width: 400px;
padding: 2rem;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
background: #fff;
}
</style>
</head>
<body>
<div class="login-card">
<h3 class="text-center mb-4 fw-bold">Laundry Admin</h3>
<?php if ($error): ?>
<div class="alert alert-danger"><?= $error ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label class="form-label"><?= __('username') ?></label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-4">
<label class="form-label"><?= __('password') ?></label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 fw-bold" style="border-radius: 12px;"><?= __('login') ?></button>
</form>
<div class="text-center mt-4">
<a href="?lang=en">English</a> | <a href="?lang=ar">العربية</a>
</div>
</div>
</body>
</html>

5
logout.php Normal file
View File

@ -0,0 +1,5 @@
<?php
session_start();
session_destroy();
header('Location: login.php');
exit;

237
order_details.php Normal file
View File

@ -0,0 +1,237 @@
<?php
// ACTION HANDLING FIRST
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.php';
$order_id = $_GET['id'] ?? null;
if (!$order_id) {
header('Location: orders.php');
exit;
}
$stmt = db()->prepare("SELECT o.*, c.name_en as customer_name_en, c.name_ar as customer_name_ar, c.phone as customer_phone, c.address_en as customer_address_en, c.address_ar as customer_address_ar
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE o.id = ?");
$stmt->execute([$order_id]);
$order = $stmt->fetch();
if (!$order) {
echo "Order not found";
exit;
}
// Handle status updates
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'update_status') {
$new_status = $_POST['status'];
$stmt = db()->prepare("UPDATE orders SET status = ? WHERE id = ?");
$stmt->execute([$new_status, $order_id]);
header("Location: order_details.php?id=$order_id");
exit;
} elseif ($_POST['action'] === 'add_payment') {
$amount = $_POST['amount'];
$method = $_POST['payment_method'];
$stmt = db()->prepare("INSERT INTO payments (order_id, amount, payment_method) VALUES (?, ?, ?)");
$stmt->execute([$order_id, $amount, $method]);
$stmt = db()->prepare("SELECT SUM(amount) as total_paid FROM payments WHERE order_id = ?");
$stmt->execute([$order_id]);
$total_paid = $stmt->fetch()['total_paid'] ?? 0;
$payment_status = $total_paid >= $order['total_price'] ? 'paid' : ($total_paid > 0 ? 'partially_paid' : 'unpaid');
$stmt = db()->prepare("UPDATE orders SET payment_status = ? WHERE id = ?");
$stmt->execute([$payment_status, $order_id]);
header("Location: order_details.php?id=$order_id");
exit;
}
}
// NOW Include header
$title = 'order_details';
require_once __DIR__ . '/includes/header.php';
$stmt = db()->prepare("SELECT oi.*, i.name_en as item_en, i.name_ar as item_ar,
s.name_en as service_en, s.name_ar as service_ar,
v.name_en as variant_en, v.name_ar as variant_ar
FROM order_items oi
JOIN items i ON oi.item_id = i.id
JOIN services s ON oi.service_id = s.id
LEFT JOIN item_variants v ON oi.variant_id = v.id
WHERE oi.order_id = ?");
$stmt->execute([$order_id]);
$order_items = $stmt->fetchAll();
$stmt = db()->prepare("SELECT * FROM payments WHERE order_id = ?");
$stmt->execute([$order_id]);
$payments = $stmt->fetchAll();
?>
<div class="row">
<div class="col-md-8">
<div class="card p-4 mb-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="fw-bold mb-0"><?= __('order') ?> #<?= $order['order_number'] ?></h5>
<span class="badge bg-<?= getStatusColor($order['status']) ?> fs-6 px-3 py-2"><?= __($order['status']) ?></span>
</div>
<div class="table-responsive mb-4">
<table class="table align-middle">
<thead>
<tr>
<th><?= __('item') ?></th>
<th><?= __('service') ?></th>
<th><?= __('quantity') ?></th>
<th class="text-end"><?= __('price') ?></th>
<th class="text-end"><?= __('vat') ?></th>
<th class="text-end"><?= __('subtotal') ?></th>
</tr>
</thead>
<tbody>
<?php
$subtotal_sum = 0;
foreach($order_items as $item):
$subtotal_sum += $item['subtotal'];
?>
<tr>
<td>
<?= $lang === 'ar' ? ($item['item_ar'] ?: $item['item_en']) : $item['item_en'] ?>
<?php if($item['variant_id']): ?>
<br><small class="text-muted"><?= $lang === 'ar' ? $item['variant_ar'] : $item['variant_en'] ?></small>
<?php endif; ?>
</td>
<td><?= $lang === 'ar' ? ($item['service_ar'] ?: $item['service_en']) : $item['service_en'] ?></td>
<td><?= $item['quantity'] ?></td>
<td class="text-end"><?= number_format($item['unit_price'], 2) ?> SAR</td>
<td class="text-end"><?= number_format($item['vat_amount'] * $item['quantity'], 2) ?> SAR</td>
<td class="text-end"><?= number_format($item['subtotal'] + ($item['vat_amount'] * $item['quantity']), 2) ?> SAR</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<th colspan="5" class="text-end"><?= __('subtotal') ?></th>
<th class="text-end"><?= number_format($subtotal_sum, 2) ?> SAR</th>
</tr>
<tr>
<th colspan="5" class="text-end"><?= __('vat_total') ?? 'VAT Total' ?></th>
<th class="text-end"><?= number_format($order['vat_total'], 2) ?> SAR</th>
</tr>
<tr>
<th colspan="5" class="text-end"><?= __('total') ?></th>
<th class="text-end fs-5 text-primary"><?= number_format($order['total_price'], 2) ?> SAR</th>
</tr>
</tfoot>
</table>
</div>
<div class="row mt-4">
<div class="col-md-6">
<h6 class="fw-bold"><?= __('customer_details') ?></h6>
<p class="mb-1"><strong><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></strong></p>
<p class="mb-1"><?= $order['customer_phone'] ?></p>
<p class="mb-0 text-muted"><?= $lang === 'ar' ? ($order['customer_address_ar'] ?: $order['customer_address_en']) : $order['customer_address_en'] ?></p>
</div>
<div class="col-md-6 text-md-end">
<h6 class="fw-bold"><?= __('order_date') ?></h6>
<p><?= date('d M Y, h:i A', strtotime($order['created_at'])) ?></p>
</div>
</div>
</div>
<div class="card p-4">
<h5 class="fw-bold mb-4"><?= __('payments') ?></h5>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th><?= __('date') ?></th>
<th><?= __('method') ?></th>
<th class="text-end"><?= __('amount') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($payments as $p): ?>
<tr>
<td><?= date('d M Y, h:i A', strtotime($p['created_at'])) ?></td>
<td><?= __($p['payment_method']) ?></td>
<td class="text-end"><?= number_format($p['amount'], 2) ?> SAR</td>
</tr>
<?php endforeach; ?>
<?php if (empty($payments)): ?>
<tr>
<td colspan="3" class="text-center text-muted"><?= __('no_payments') ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card p-4 mb-4">
<h5 class="fw-bold mb-4"><?= __('actions') ?></h5>
<form method="POST" class="mb-4">
<input type="hidden" name="action" value="update_status">
<label class="form-label small text-muted"><?= __('update_status') ?></label>
<div class="input-group">
<select name="status" class="form-select" style="border-radius: 12px 0 0 12px;">
<option value="received" <?= $order['status'] == 'received' ? 'selected' : '' ?>><?= __('received') ?></option>
<option value="processing" <?= $order['status'] == 'processing' ? 'selected' : '' ?>><?= __('processing') ?></option>
<option value="ready" <?= $order['status'] == 'ready' ? 'selected' : '' ?>><?= __('ready') ?></option>
<option value="delivered" <?= $order['status'] == 'delivered' ? 'selected' : '' ?>><?= __('delivered') ?></option>
<option value="cancelled" <?= $order['status'] == 'cancelled' ? 'selected' : '' ?>><?= __('cancelled') ?></option>
</select>
<button type="submit" class="btn btn-primary" style="border-radius: 0 12px 12px 0;"><?= __('update') ?></button>
</div>
</form>
<hr>
<?php
$stmt = db()->prepare("SELECT SUM(amount) as total_paid FROM payments WHERE order_id = ?");
$stmt->execute([$order_id]);
$total_paid = $stmt->fetch()['total_paid'] ?? 0;
$remaining = $order['total_price'] - $total_paid;
?>
<div class="alert alert-<?= $remaining <= 0 ? 'success' : 'warning' ?> p-2 px-3 small rounded-4">
<?= __('remaining_amount') ?>: <strong><?= number_format(max(0, $remaining), 2) ?> SAR</strong>
</div>
<?php if ($remaining > 0): ?>
<form method="POST">
<input type="hidden" name="action" value="add_payment">
<div class="mb-3">
<label class="form-label small text-muted"><?= __('amount') ?></label>
<input type="number" step="0.01" name="amount" class="form-control" value="<?= $remaining ?>" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small text-muted"><?= __('payment_method') ?></label>
<select name="payment_method" class="form-select" style="border-radius: 12px;">
<option value="cash"><?= __('cash') ?></option>
<option value="card"><?= __('card') ?></option>
<option value="transfer"><?= __('transfer') ?></option>
</select>
</div>
<button type="submit" class="btn btn-success w-100 py-2" style="border-radius: 12px;"><?= __('add_payment') ?></button>
</form>
<?php endif; ?>
<button class="btn btn-outline-dark w-100 mt-3 py-2" style="border-radius: 12px;" onclick="window.print()">
<i class="bi bi-printer me-1"></i> <?= __('print_invoice') ?>
</button>
</div>
</div>
</div>
<?php
function getStatusColor($status) {
return [
'received' => 'secondary',
'processing' => 'primary',
'ready' => 'success',
'delivered' => 'dark',
'cancelled' => 'danger',
][$status] ?? 'info';
}
require_once __DIR__ . '/includes/footer.php';
?>

122
orders.php Normal file
View File

@ -0,0 +1,122 @@
<?php
$title = 'orders';
require_once __DIR__ . '/includes/header.php';
$branch_id = $_SESSION['branch_id'];
$status_filter = $_GET['status'] ?? '';
$payment_filter = $_GET['payment_status'] ?? '';
$sql = "SELECT o.*, c.name_en as customer_name_en, c.name_ar as customer_name_ar, c.phone as customer_phone
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE o.branch_id = ?";
$params = [$branch_id];
if ($status_filter) {
$sql .= " AND o.status = ?";
$params[] = $status_filter;
}
if ($payment_filter) {
$sql .= " AND o.payment_status = ?";
$params[] = $payment_filter;
}
$sql .= " ORDER BY o.created_at DESC";
$stmt = db()->prepare($sql);
$stmt->execute($params);
$orders = $stmt->fetchAll();
?>
<div class="card p-4">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
<h5 class="fw-bold mb-3 mb-md-0"><?= __('orders_list') ?? 'Orders List' ?></h5>
<div class="d-flex gap-2">
<form action="" method="GET" class="d-flex gap-2">
<select name="status" class="form-select border-radius-12" style="border-radius: 12px; width: auto;" onchange="this.form.submit()">
<option value=""><?= __('all_status') ?? 'All Status' ?></option>
<option value="received" <?= $status_filter == 'received' ? 'selected' : '' ?>><?= __('received') ?></option>
<option value="processing" <?= $status_filter == 'processing' ? 'selected' : '' ?>><?= __('processing') ?></option>
<option value="ready" <?= $status_filter == 'ready' ? 'selected' : '' ?>><?= __('ready') ?></option>
<option value="delivered" <?= $status_filter == 'delivered' ? 'selected' : '' ?>><?= __('delivered') ?></option>
<option value="cancelled" <?= $status_filter == 'cancelled' ? 'selected' : '' ?>><?= __('cancelled') ?></option>
</select>
<select name="payment_status" class="form-select border-radius-12" style="border-radius: 12px; width: auto;" onchange="this.form.submit()">
<option value=""><?= __('all_payments') ?? 'All Payments' ?></option>
<option value="unpaid" <?= $payment_filter == 'unpaid' ? 'selected' : '' ?>><?= __('unpaid') ?></option>
<option value="partially_paid" <?= $payment_filter == 'partially_paid' ? 'selected' : '' ?>><?= __('partially_paid') ?></option>
<option value="paid" <?= $payment_filter == 'paid' ? 'selected' : '' ?>><?= __('paid') ?></option>
</select>
</form>
<a href="pos.php" class="btn btn-primary px-4" style="border-radius: 12px;">
<i class="bi bi-plus-lg me-1"></i> <?= __('new_order') ?>
</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>#</th>
<th><?= __('order_number') ?? 'Order #' ?></th>
<th><?= __('customer') ?></th>
<th><?= __('total') ?></th>
<th><?= __('status') ?></th>
<th><?= __('payment_status') ?></th>
<th><?= __('date') ?></th>
<th><?= __('actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($orders as $order): ?>
<tr>
<td><?= $order['id'] ?></td>
<td class="fw-bold"><?= $order['order_number'] ?></td>
<td>
<div class="fw-bold"><?= $lang === 'ar' ? ($order['customer_name_ar'] ?: $order['customer_name_en']) : $order['customer_name_en'] ?></div>
<div class="small text-muted"><?= $order['customer_phone'] ?></div>
</td>
<td class="fw-bold text-primary"><?= number_format($order['total_price'], 2) ?></td>
<td><span class="badge bg-<?= getStatusColor($order['status']) ?> rounded-pill px-3 py-2"><?= __($order['status']) ?></span></td>
<td><span class="badge bg-<?= getPaymentStatusColor($order['payment_status']) ?> rounded-pill px-3 py-2"><?= __($order['payment_status']) ?></span></td>
<td class="small"><?= date('d/m/Y H:i', strtotime($order['created_at'])) ?></td>
<td>
<a href="order_details.php?id=<?= $order['id'] ?>" class="btn btn-sm btn-light border-0 p-2" style="border-radius: 8px;">
<i class="bi bi-eye-fill"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($orders)): ?>
<tr>
<td colspan="8" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-3 opacity-25"></i>
<?= __('no_orders_found') ?? 'No orders found' ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php
function getStatusColor($status) {
return [
'received' => 'secondary',
'processing' => 'primary',
'ready' => 'success',
'delivered' => 'dark',
'cancelled' => 'danger',
][$status] ?? 'info';
}
function getPaymentStatusColor($status) {
return [
'unpaid' => 'danger',
'partially_paid' => 'warning',
'paid' => 'success',
][$status] ?? 'info';
}
require_once __DIR__ . '/includes/footer.php';
?>

468
pos.php Normal file
View File

@ -0,0 +1,468 @@
<?php
$title = 'pos';
require_once __DIR__ . '/includes/header.php';
$branch_id = $_SESSION['branch_id'];
// Get all categories
$categories = db()->query("SELECT * FROM categories ORDER BY name_en ASC")->fetchAll();
// Get all items with details
$stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar
FROM items i
LEFT JOIN categories c ON i.category_id = c.id
ORDER BY i.name_en ASC");
$stmt->execute();
$items_raw = $stmt->fetchAll();
// Get all services and prices for this branch
$stmt = db()->prepare("SELECT p.item_id, p.variant_id, p.service_id, p.price,
s.name_en as service_en, s.name_ar as service_ar,
v.name_en as variant_en, v.name_ar as variant_ar
FROM prices p
JOIN services s ON p.service_id = s.id
LEFT JOIN item_variants v ON p.variant_id = v.id
WHERE p.branch_id = ?");
$stmt->execute([$branch_id]);
$all_prices = $stmt->fetchAll();
$items = [];
foreach ($items_raw as $i) {
$items[$i['id']] = [
'id' => $i['id'],
'name_en' => $i['name_en'],
'name_ar' => $i['name_ar'],
'category_id' => $i['category_id'],
'image_url' => $i['image_url'],
'vat_percent' => (float)$i['vat_percent'],
'variants' => [],
'default_services' => []
];
}
foreach ($all_prices as $p) {
if (!isset($items[$p['item_id']])) continue;
if ($p['variant_id']) {
if (!isset($items[$p['item_id']]['variants'][$p['variant_id']])) {
$items[$p['item_id']]['variants'][$p['variant_id']] = [
'id' => $p['variant_id'],
'name_en' => $p['variant_en'],
'name_ar' => $p['variant_ar'],
'services' => []
];
}
$items[$p['item_id']]['variants'][$p['variant_id']]['services'][] = [
'id' => $p['service_id'],
'name_en' => $p['service_en'],
'name_ar' => $p['service_ar'],
'price' => (float)$p['price']
];
} else {
$items[$p['item_id']]['default_services'][] = [
'id' => $p['service_id'],
'name_en' => $p['service_en'],
'name_ar' => $p['service_ar'],
'price' => (float)$p['price']
];
}
}
// Get all customers for this branch
$stmt = db()->prepare("SELECT * FROM customers WHERE branch_id = ? ORDER BY name_en ASC");
$stmt->execute([$branch_id]);
$customers = $stmt->fetchAll();
?>
<div class="row">
<div class="col-md-8">
<!-- Category Filter -->
<div class="mb-4 overflow-auto d-flex pb-2" style="white-space: nowrap;">
<button class="btn btn-primary me-2 cat-filter active" data-cat="all" style="border-radius: 12px; min-width: 80px;">
<?= __('all') ?? 'All' ?>
</button>
<?php foreach($categories as $cat): ?>
<button class="btn btn-outline-secondary me-2 cat-filter" data-cat="<?= $cat['id'] ?>" style="border-radius: 12px; min-width: 80px;">
<?= $lang === 'ar' ? $cat['name_ar'] : $cat['name_en'] ?>
</button>
<?php endforeach; ?>
</div>
<div class="card p-4 mb-4 border-0 shadow-sm" style="border-radius: 25px;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="fw-bold mb-0"><?= __('select_items') ?></h5>
<div class="input-group w-50 shadow-sm rounded-4" style="overflow: hidden;">
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search"></i></span>
<input type="text" id="itemSearch" class="form-control border-start-0 py-2" placeholder="<?= __('search_items') ?>">
</div>
</div>
<div class="row g-3" id="itemsList">
<?php foreach($items as $item): ?>
<?php if (empty($item['default_services']) && empty($item['variants'])) continue; ?>
<div class="col-md-4 col-sm-6 item-card-wrapper"
data-cat="<?= $item['category_id'] ?>"
data-name-en="<?= strtolower($item['name_en']) ?>"
data-name-ar="<?= $item['name_ar'] ?>">
<div class="card h-100 border-0 item-card cursor-pointer shadow-sm overflow-hidden" onclick="showOptions(<?= $item['id'] ?>)">
<div class="position-relative">
<?php if($item['image_url']): ?>
<img src="<?= $item['image_url'] ?>?v=<?= time() ?>" class="card-img-top" style="height: 140px; object-fit: cover;">
<?php else: ?>
<div class="bg-light text-center py-4 d-flex align-items-center justify-content-center" style="height: 140px;">
<i class="bi bi-bag-check fs-1 text-muted opacity-50"></i>
</div>
<?php endif; ?>
<div class="position-absolute top-0 end-0 m-2">
<span class="badge bg-white text-dark shadow-sm rounded-pill px-3 py-2 small"><?= $item['vat_percent'] ?>% VAT</span>
</div>
</div>
<div class="card-body p-3 text-center">
<div class="fw-bold fs-6"><?= $lang === 'ar' ? $item['name_ar'] : $item['name_en'] ?></div>
<small class="text-muted d-block mt-1"><?= count($item['default_services']) + count($item['variants']) ?> <?= __('options') ?? 'Options' ?></small>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card p-4 sticky-top border-0 shadow-sm" style="top: 2rem; max-height: 90vh; overflow-y: auto; border-radius: 25px;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="fw-bold mb-0"><?= __('cart') ?></h5>
<button class="btn btn-sm btn-light text-danger rounded-pill px-3" onclick="clearCart()">
<i class="bi bi-trash me-1"></i> <?= __('clear') ?? 'Clear' ?>
</button>
</div>
<div class="mb-4">
<label class="form-label small text-muted fw-bold"><?= __('customer') ?></label>
<div class="d-flex">
<select id="customerId" class="form-select shadow-sm" style="border-radius: 12px; border: 1px solid #eee;">
<option value=""><?= __('walk_in_customer') ?></option>
<?php foreach($customers as $c): ?>
<option value="<?= $c['id'] ?>"><?= $c['phone'] ?> - <?= $lang === 'ar' ? ($c['name_ar'] ?: $c['name_en']) : $c['name_en'] ?></option>
<?php endforeach; ?>
</select>
<button class="btn btn-primary ms-2 shadow-sm" style="border-radius: 12px;" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
<div id="cartItems" class="mb-4" style="min-height: 200px;">
<div class="text-center text-muted mt-5" id="emptyCart">
<i class="bi bi-cart-x fs-1 opacity-25"></i>
<p class="mt-2"><?= __('cart_is_empty') ?></p>
</div>
</div>
<div class="border-top pt-4 mt-auto">
<div class="d-flex justify-content-between mb-2">
<span class="text-muted"><?= __('subtotal') ?></span>
<span class="fw-bold" id="cartSubtotal">0.00 SAR</span>
</div>
<div class="d-flex justify-content-between mb-3">
<span class="text-muted"><?= __('vat') ?? 'VAT' ?></span>
<span class="fw-bold" id="cartVat">0.00 SAR</span>
</div>
<div class="d-flex justify-content-between mb-4 p-3 bg-light rounded-4">
<span class="fw-bold fs-5"><?= __('total') ?></span>
<span class="fw-bold fs-5 text-primary" id="cartTotal">0.00 SAR</span>
</div>
<button class="btn btn-primary w-100 py-3 fw-bold shadow-sm" style="border-radius: 18px;" onclick="checkout()">
<i class="bi bi-shield-check me-2"></i><?= __('checkout') ?>
</button>
</div>
</div>
</div>
</div>
<!-- Selection Modal (Service Options) -->
<div class="modal fade" id="selectionModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg" style="border-radius: 25px;">
<div class="modal-header border-0 pb-0 p-4">
<div>
<h5 class="modal-title fw-bold" id="selectionItemName"></h5>
<p class="text-muted small m-0"><?= __('select_service_option') ?? 'Select service type' ?></p>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4 pt-3">
<div class="list-group list-group-flush" id="optionsList">
<!-- Service options will be loaded here -->
</div>
</div>
</div>
</div>
</div>
<!-- Add Customer Modal -->
<div class="modal fade" id="addCustomerModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0" style="border-radius: 25px;">
<div class="modal-header border-0 p-4 pb-0">
<h5 class="modal-title fw-bold"><?= __('add_customer') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form id="customerForm">
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('phone') ?> *</label>
<input type="text" id="custPhone" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('name_en') ?> *</label>
<input type="text" id="custNameEn" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('name_ar') ?></label>
<input type="text" id="custNameAr" class="form-control" style="border-radius: 12px;">
</div>
<button type="button" class="btn btn-primary w-100 py-3 fw-bold" style="border-radius: 15px;" onclick="saveCustomer()">
<?= __('save') ?>
</button>
</form>
</div>
</div>
</div>
</div>
<script>
const itemsData = <?= json_encode($items) ?>;
const lang = '<?= $lang ?>';
let cart = JSON.parse(localStorage.getItem('pos_cart') || '[]');
const selectionModal = new bootstrap.Modal(document.getElementById('selectionModal'));
function showOptions(itemId) {
const item = itemsData[itemId];
document.getElementById('selectionItemName').innerText = lang === 'ar' ? item.name_ar : item.name_en;
const optionsList = document.getElementById('optionsList');
optionsList.innerHTML = '';
// 1. Add Default Services
item.default_services.forEach(service => {
addOptionButton(item, null, service);
});
// 2. Add Variant Services
Object.values(item.variants).forEach(variant => {
variant.services.forEach(service => {
addOptionButton(item, variant, service);
});
});
selectionModal.show();
}
function addOptionButton(item, variant, service) {
const optionsList = document.getElementById('optionsList');
const btn = document.createElement('button');
btn.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center border-0 py-3 mb-2 rounded-4 bg-light shadow-sm-hover transition';
let variantText = variant ? (lang === 'ar' ? variant.name_ar : variant.name_en) : '';
let serviceText = lang === 'ar' ? service.name_ar : service.name_en;
btn.innerHTML = `
<div>
${variantText ? `<div class="badge bg-primary text-white mb-1 small rounded-pill px-2" style="font-size: 0.7rem;">${variantText}</div>` : ''}
<div class="fw-bold">${serviceText}</div>
</div>
<div class="text-primary fw-bold fs-5">${service.price.toFixed(2)} <small style="font-size: 0.8rem;">SAR</small></div>
`;
btn.onclick = () => addToCart(item, variant, service);
optionsList.appendChild(btn);
}
function addToCart(item, variant, service) {
const variantId = variant ? variant.id : null;
const cartItemId = `${item.id}-${variantId}-${service.id}`;
const existing = cart.find(i => i.cartItemId === cartItemId);
const vatPercent = item.vat_percent || 0;
const vatAmount = (service.price * (vatPercent / 100));
if (existing) {
existing.quantity++;
} else {
cart.push({
cartItemId,
itemId: item.id,
variantId: variantId,
serviceId: service.id,
itemName: lang === 'ar' ? item.name_ar : item.name_en,
variantName: variant ? (lang === 'ar' ? variant.name_ar : variant.name_en) : null,
serviceName: lang === 'ar' ? service.name_ar : service.name_en,
price: service.price,
vatPercent: vatPercent,
vatAmount: vatAmount,
quantity: 1
});
}
updateCart();
selectionModal.hide();
}
function updateCart() {
localStorage.setItem('pos_cart', JSON.stringify(cart));
const cartList = document.getElementById('cartItems');
const emptyCart = document.getElementById('emptyCart');
cartList.querySelectorAll('.cart-item-row').forEach(e => e.remove());
if (cart.length === 0) {
emptyCart.style.display = 'block';
} else {
emptyCart.style.display = 'none';
cart.forEach((item, index) => {
const row = document.createElement('div');
row.className = 'cart-item-row d-flex justify-content-between align-items-center mb-3 p-3 bg-white border rounded-4 shadow-sm';
row.innerHTML = `
<div style="flex: 1;">
<div class="fw-bold small">${item.itemName}${item.variantName ? ' <span class="badge bg-soft-primary text-primary mx-1" style="font-size: 0.65rem;">' + item.variantName + '</span>' : ''}</div>
<div class="text-muted" style="font-size: 0.75rem;">${item.serviceName}</div>
</div>
<div class="d-flex align-items-center mx-3 bg-light rounded-pill px-2 py-1">
<button class="btn btn-sm p-0 text-muted" onclick="changeQty(${index}, -1)"><i class="bi bi-dash-circle-fill"></i></button>
<span class="mx-3 fw-bold" style="min-width: 15px; text-align: center;">${item.quantity}</span>
<button class="btn btn-sm p-0 text-primary" onclick="changeQty(${index}, 1)"><i class="bi bi-plus-circle-fill"></i></button>
</div>
<div class="fw-bold text-end" style="width: 70px;">
${(item.price * item.quantity).toFixed(2)}
</div>
`;
cartList.appendChild(row);
});
}
const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const vat = cart.reduce((sum, item) => sum + (item.vatAmount * item.quantity), 0);
const total = subtotal + vat;
document.getElementById('cartSubtotal').innerText = subtotal.toFixed(2) + ' SAR';
document.getElementById('cartVat').innerText = vat.toFixed(2) + ' SAR';
document.getElementById('cartTotal').innerText = total.toFixed(2) + ' SAR';
}
function changeQty(index, delta) {
cart[index].quantity += delta;
if (cart[index].quantity <= 0) {
cart.splice(index, 1);
}
updateCart();
}
function clearCart() {
if (confirm('Are you sure you want to clear the cart?')) {
cart = [];
updateCart();
}
}
function checkout() {
if (cart.length === 0) { alert('Cart is empty'); return; }
const customerId = document.getElementById('customerId').value;
const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const vat = cart.reduce((sum, item) => sum + (item.vatAmount * item.quantity), 0);
fetch('api/checkout.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customer_id: customerId,
items: cart,
vat_total: vat,
total_price: subtotal + vat
})
})
.then(r => r.json())
.then(res => {
if (res.success) {
cart = [];
localStorage.removeItem('pos_cart');
window.location.href = 'order_details.php?id=' + res.order_id;
} else alert('Error: ' + res.error);
});
}
function saveCustomer() {
const phone = document.getElementById('custPhone').value;
const name_en = document.getElementById('custNameEn').value;
const name_ar = document.getElementById('custNameAr').value;
if (!phone || !name_en) { alert('Please fill required fields'); return; }
const formData = new FormData();
formData.append('phone', phone);
formData.append('name_en', name_en);
formData.append('name_ar', name_ar);
fetch('api/add_customer.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(res => {
if (res.success) {
const select = document.getElementById('customerId');
const opt = document.createElement('option');
opt.value = res.id;
opt.text = phone + ' - ' + (lang === 'ar' ? (name_ar || name_en) : name_en);
opt.selected = true;
select.add(opt);
bootstrap.Modal.getInstance(document.getElementById('addCustomerModal')).hide();
} else alert('Error: ' + res.error);
});
}
// Category filter
document.querySelectorAll('.cat-filter').forEach(btn => {
btn.onclick = () => {
document.querySelectorAll('.cat-filter').forEach(b => b.classList.remove('active', 'btn-primary'));
document.querySelectorAll('.cat-filter').forEach(b => b.classList.add('btn-outline-secondary'));
btn.classList.add('active', 'btn-primary');
btn.classList.remove('btn-outline-secondary');
const cat = btn.getAttribute('data-cat');
document.querySelectorAll('.item-card-wrapper').forEach(card => {
if (cat === 'all' || card.getAttribute('data-cat') === cat) card.style.display = 'block';
else card.style.display = 'none';
});
};
});
// Item search
document.getElementById('itemSearch').addEventListener('input', function(e) {
const q = e.target.value.toLowerCase();
document.querySelectorAll('.item-card-wrapper').forEach(card => {
const en = card.getAttribute('data-name-en');
const ar = card.getAttribute('data-name-ar');
if (en.includes(q) || ar.includes(q)) card.style.display = 'block';
else card.style.display = 'none';
});
});
// Initialize cart on load
updateCart();
</script>
<style>
.cursor-pointer { cursor: pointer; }
.item-card { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border-radius: 25px; border: 1px solid transparent; }
.item-card:hover { transform: translateY(-8px); border-color: var(--bs-primary) !important; box-shadow: 0 15px 30px rgba(13, 110, 253, 0.15) !important; }
.bg-light { background-color: #f8f9fa !important; }
.rounded-4 { border-radius: 1rem !important; }
.cat-filter.active { color: white !important; }
.bg-soft-primary { background-color: rgba(13, 110, 253, 0.1); }
.list-group-item-action:hover { background-color: #e9ecef !important; transform: scale(1.02); transition: all 0.2s; }
.transition { transition: all 0.2s; }
.shadow-sm-hover:hover { shadow: 0 .125rem .25rem rgba(0,0,0,.075)!important; }
</style>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

123
users.php Normal file
View File

@ -0,0 +1,123 @@
<?php
// ACTION HANDLING FIRST
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/lang.php';
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$current_role = $_SESSION['role'] ?? 'cashier';
if ($current_role === 'cashier') {
header('Location: admin.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
if ($_POST['action'] === 'add_user') {
$username = $_POST['username'];
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$full_name_en = $_POST['full_name_en'];
$full_name_ar = $_POST['full_name_ar'];
$role = $_POST['role'];
$branch_id = $_POST['branch_id'] ?: null;
$company_id = 1; // Default for now
$stmt = db()->prepare("INSERT INTO users (username, password_hash, full_name_en, full_name_ar, role, branch_id, company_id) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$username, $password, $full_name_en, $full_name_ar, $role, $branch_id, $company_id]);
header('Location: users.php');
exit;
}
}
// NOW Include header
$title = 'users';
require_once __DIR__ . '/includes/header.php';
$users = db()->query("SELECT u.*, b.name_en as branch_name_en FROM users u LEFT JOIN branches b ON u.branch_id = b.id")->fetchAll();
$branches = db()->query("SELECT * FROM branches")->fetchAll();
?>
<div class="row">
<div class="col-md-4">
<div class="card p-4 border-0 shadow-sm" style="border-radius: 20px;">
<h5 class="fw-bold mb-4"><?= __('add_new_user') ?? 'Add New User' ?></h5>
<form method="POST">
<input type="hidden" name="action" value="add_user">
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('username') ?></label>
<input type="text" name="username" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('password') ?></label>
<input type="password" name="password" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('full_name_en') ?></label>
<input type="text" name="full_name_en" class="form-control" required style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('full_name_ar') ?></label>
<input type="text" name="full_name_ar" class="form-control" style="border-radius: 12px;">
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('role') ?></label>
<select name="role" class="form-select" style="border-radius: 12px;">
<option value="super_admin">Super Admin</option>
<option value="branch_manager">Branch Manager</option>
<option value="cashier" selected>Cashier</option>
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('branch') ?></label>
<select name="branch_id" class="form-select" style="border-radius: 12px;">
<option value="">None (Super Admin)</option>
<?php foreach($branches as $b): ?>
<option value="<?= $b['id'] ?>"><?= $lang === 'ar' ? $b['name_ar'] : $b['name_en'] ?></option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold" style="border-radius: 15px;"><?= __('add_user') ?></button>
</form>
</div>
</div>
<div class="col-md-8">
<div class="card p-0 border-0 shadow-sm" style="border-radius: 20px; overflow: hidden;">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4 py-3">#</th>
<th class="py-3"><?= __('username') ?></th>
<th class="py-3"><?= __('name') ?></th>
<th class="py-3"><?= __('role') ?></th>
<th class="pe-4 py-3"><?= __('branch') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($users as $u): ?>
<tr>
<td class="ps-4"><?= $u['id'] ?></td>
<td><?= $u['username'] ?></td>
<td class="fw-bold"><?= $lang === 'ar' ? ($u['full_name_ar'] ?: $u['full_name_en']) : $u['full_name_en'] ?></td>
<td>
<span class="badge bg-soft-info text-info px-3 py-2" style="border-radius: 8px;">
<?= $u['role'] ?>
</span>
</td>
<td class="pe-4"><?= $u['branch_name_en'] ?: '-' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<style>
.bg-soft-info { background-color: rgba(13, 202, 240, 0.1); }
</style>
<?php require_once __DIR__ . '/includes/footer.php'; ?>