Autosave: 20260302-103011
This commit is contained in:
parent
7406216157
commit
874d750960
310
admin.php
310
admin.php
@ -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
37
api/add_customer.php
Normal 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()]);
|
||||
}
|
||||
24
api/add_customer_redirect.php
Normal file
24
api/add_customer_redirect.php
Normal 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
59
api/checkout.php
Normal 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()]);
|
||||
}
|
||||
@ -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
96
branches.php
Normal 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
110
customers.php
Normal 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'; ?>
|
||||
129
db/migrations/01_initial_schema.sql
Normal file
129
db/migrations/01_initial_schema.sql
Normal 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);
|
||||
37
db/migrations/02_update_items.sql
Normal file
37
db/migrations/02_update_items.sql
Normal 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', 'ملاءات السرير');
|
||||
7
db/migrations/03_update_orders.sql
Normal file
7
db/migrations/03_update_orders.sql
Normal 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;
|
||||
3
db/migrations/04_fix_prices_key.sql
Normal file
3
db/migrations/04_fix_prices_key.sql
Normal 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
8
includes/footer.php
Normal 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
158
includes/header.php
Normal 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
200
includes/lang.php
Normal 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';
|
||||
}
|
||||
58
index.php
58
index.php
@ -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
860
items.php
Normal 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
86
login.php
Normal 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
5
logout.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_destroy();
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
237
order_details.php
Normal file
237
order_details.php
Normal 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
122
orders.php
Normal 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
468
pos.php
Normal 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
123
users.php
Normal 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'; ?>
|
||||
Loading…
x
Reference in New Issue
Block a user