353 lines
18 KiB
PHP
353 lines
18 KiB
PHP
<?php
|
|
require_once __DIR__ . '/db/config.php';
|
|
require_once __DIR__ . '/includes/lang.php';
|
|
|
|
if (!isset($_SESSION['user_id'])) {
|
|
header('Location: login.php');
|
|
exit;
|
|
}
|
|
|
|
if (!has_permission('view')) {
|
|
header('Location: admin.php');
|
|
exit;
|
|
}
|
|
|
|
$branch_id = $_SESSION['branch_id'];
|
|
$loyalty_enabled = get_setting('loyalty_enabled', '0');
|
|
|
|
// Handle Actions
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
$action = $_POST['action'];
|
|
$required_permission = 'view';
|
|
if ($action === 'add_customer') $required_permission = 'add';
|
|
if ($action === 'edit_customer') $required_permission = 'edit';
|
|
if ($action === 'delete_customer') $required_permission = 'delete';
|
|
if ($action === 'adjust_points') $required_permission = 'edit';
|
|
|
|
if (!has_permission($required_permission)) {
|
|
header('Location: customers.php?error=no_permission');
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'add_customer') {
|
|
$phone = $_POST['phone'] ?? '';
|
|
$name_en = $_POST['name_en'] ?? '';
|
|
$name_ar = $_POST['name_ar'] ?? '';
|
|
$email = $_POST['email'] ?? '';
|
|
$address_en = $_POST['address_en'] ?? '';
|
|
$address_ar = $_POST['address_ar'] ?? '';
|
|
|
|
if ($phone && $name_en) {
|
|
$stmt = db()->prepare("INSERT INTO customers (phone, name_en, name_ar, email, address_en, address_ar) VALUES (?, ?, ?, ?, ?, ?)");
|
|
$stmt->execute([$phone, $name_en, $name_ar, $email, $address_en, $address_ar]);
|
|
header('Location: customers.php?success=customer_added');
|
|
exit;
|
|
}
|
|
} elseif ($action === 'edit_customer') {
|
|
$id = $_POST['id'];
|
|
$phone = $_POST['phone'] ?? '';
|
|
$name_en = $_POST['name_en'] ?? '';
|
|
$name_ar = $_POST['name_ar'] ?? '';
|
|
$email = $_POST['email'] ?? '';
|
|
$address_en = $_POST['address_en'] ?? '';
|
|
$address_ar = $_POST['address_ar'] ?? '';
|
|
|
|
$stmt = db()->prepare("UPDATE customers SET phone = ?, name_en = ?, name_ar = ?, email = ?, address_en = ?, address_ar = ? WHERE id = ?");
|
|
$stmt->execute([$phone, $name_en, $name_ar, $email, $address_en, $address_ar, $id]);
|
|
header('Location: customers.php?success=customer_updated');
|
|
exit;
|
|
} elseif ($action === 'delete_customer') {
|
|
$id = $_POST['id'];
|
|
$stmt = db()->prepare("SELECT COUNT(*) FROM orders WHERE customer_id = ?");
|
|
$stmt->execute([$id]);
|
|
if ($stmt->fetchColumn() > 0) {
|
|
header('Location: customers.php?error=customer_has_orders');
|
|
exit;
|
|
}
|
|
$stmt = db()->prepare("DELETE FROM customers WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
header('Location: customers.php?success=customer_deleted');
|
|
exit;
|
|
} elseif ($action === 'adjust_points') {
|
|
$id = $_POST['id'];
|
|
$points = (float)$_POST['points'];
|
|
$type = $_POST['type']; // 'add' or 'subtract'
|
|
$desc = $_POST['description'] ?? 'Manual adjustment';
|
|
|
|
$pdo = db();
|
|
$pdo->beginTransaction();
|
|
try {
|
|
$val = ($type === 'add') ? $points : -$points;
|
|
$pdo->prepare("UPDATE customers SET loyalty_points = loyalty_points + ? WHERE id = ?")->execute([$val, $id]);
|
|
$pdo->prepare("INSERT INTO loyalty_transactions (customer_id, points, type, description) VALUES (?, ?, 'adjusted', ?)")
|
|
->execute([$id, $val, $desc]);
|
|
$pdo->commit();
|
|
header('Location: customers.php?success=points_adjusted');
|
|
exit;
|
|
} catch (Exception $e) {
|
|
$pdo->rollBack();
|
|
header('Location: customers.php?error=' . urlencode($e->getMessage()));
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
$title = 'customers';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
$search = $_GET['search'] ?? '';
|
|
|
|
$sql = "SELECT * FROM customers WHERE 1=1";
|
|
$params = [];
|
|
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 border-0 shadow-sm" style="border-radius: 20px;">
|
|
<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 shadow-sm rounded-4 overflow-hidden border">
|
|
<input type="text" name="search" class="form-control border-0 py-2 shadow-none" placeholder="<?= __('search') ?>" value="<?= htmlspecialchars($search) ?>">
|
|
<button class="btn btn-white border-0" type="submit"><i class="bi bi-search"></i></button>
|
|
</div>
|
|
</form>
|
|
<?php if (has_permission('add')): ?>
|
|
<button class="btn btn-primary px-4 shadow-sm rounded-4" onclick="openCustomerModal()">
|
|
<i class="bi bi-person-plus-fill me-1"></i> <?= __('add_new') ?>
|
|
</button>
|
|
<?php endif; ?>
|
|
</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; ?>
|
|
<?php if(isset($_GET['error'])): ?>
|
|
<div class="alert alert-danger alert-dismissible fade show rounded-4 shadow-sm mb-4 border-0" role="alert">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> <?= __($_GET['error']) ?? 'An error occurred' ?>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>#</th>
|
|
<th><?= __('name') ?></th>
|
|
<th><?= __('phone') ?></th>
|
|
<?php if ($loyalty_enabled == '1'): ?>
|
|
<th><?= __('loyalty_points') ?? 'Loyalty Points' ?></th>
|
|
<?php endif; ?>
|
|
<th><?= __('email') ?></th>
|
|
<th><?= __('date') ?></th>
|
|
<th class="text-end"><?= __('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>
|
|
<?php if ($loyalty_enabled == '1'): ?>
|
|
<td>
|
|
<span class="badge bg-primary rounded-pill px-3 py-2 pointer" onclick="openLoyaltyModal(<?= $c['id'] ?>, '<?= addslashes($lang === 'ar' ? ($c['name_ar'] ?: $c['name_en']) : $c['name_en']) ?>', <?= $c['loyalty_points'] ?>)">
|
|
<?= number_format($c['loyalty_points'], 2) ?>
|
|
</span>
|
|
</td>
|
|
<?php endif; ?>
|
|
<td><?= $c['email'] ?: '-' ?></td>
|
|
<td class="small text-muted"><?= date('d/m/Y', strtotime($c['created_at'])) ?></td>
|
|
<td class="text-end">
|
|
<div class="d-flex gap-1 justify-content-end">
|
|
<a href="customer_statement.php?id=<?= $c['id'] ?>" class="btn btn-sm btn-light border-0 p-2 text-info rounded-3" title="<?= __('statement') ?>">
|
|
<i class="bi bi-file-earmark-text-fill"></i>
|
|
</a>
|
|
<?php if (has_permission('edit')): ?>
|
|
<button class="btn btn-sm btn-light border-0 p-2 text-primary rounded-3" onclick="openCustomerModal(<?= htmlspecialchars(json_encode($c)) ?>)">
|
|
<i class="bi bi-pencil-fill"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
<?php if (has_permission('delete')): ?>
|
|
<button class="btn btn-sm btn-light border-0 p-2 text-danger rounded-3" onclick="confirmDelete('customer', <?= $c['id'] ?>)">
|
|
<i class="bi bi-trash-fill"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php if (empty($customers)): ?>
|
|
<tr><td colspan="7" class="text-center py-5 text-muted"><i class="bi bi-people fs-1 d-block mb-3 opacity-25"></i><?= __('no_customers_found') ?? 'No customers found' ?></td></tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer Modal (Add/Edit) -->
|
|
<div class="modal fade" id="customerModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 shadow-lg rounded-4">
|
|
<div class="modal-header border-0 pb-0 p-4">
|
|
<h5 class="modal-title fw-bold" id="customerModalLabel"><?= __('add_new_customer') ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body p-4">
|
|
<form id="customerForm" method="POST">
|
|
<input type="hidden" name="action" id="customerAction" value="add_customer">
|
|
<input type="hidden" name="id" id="customerId">
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold"><?= __('phone') ?></label>
|
|
<input type="text" name="phone" id="customerPhone" class="form-control rounded-3" required>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small fw-bold"><?= __('name_en') ?? 'Name (English)' ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="name_en" id="customerNameEn" class="form-control rounded-start-3" required>
|
|
<button type="button" class="btn btn-outline-secondary rounded-end-3" onclick="translateField('customerNameEn', 'customerNameAr', 'en-ar')"><i class="bi bi-translate"></i></button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small fw-bold"><?= __('name_ar') ?? 'Name (Arabic)' ?></label>
|
|
<div class="input-group">
|
|
<input type="text" name="name_ar" id="customerNameAr" class="form-control rounded-start-3">
|
|
<button type="button" class="btn btn-outline-secondary rounded-end-3" onclick="translateField('customerNameAr', 'customerNameEn', 'ar-en')"><i class="bi bi-translate"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold"><?= __('email') ?></label>
|
|
<input type="email" name="email" id="customerEmail" class="form-control rounded-3">
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small fw-bold"><?= __('address_en') ?? 'Address (English)' ?></label>
|
|
<textarea name="address_en" id="customerAddressEn" class="form-control rounded-3" rows="2"></textarea>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small fw-bold"><?= __('address_ar') ?? 'Address (Arabic)' ?></label>
|
|
<textarea name="address_ar" id="customerAddressAr" class="form-control rounded-3" rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100 py-3 mt-2 fw-bold shadow-sm rounded-4"><?= __('save') ?></button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loyalty Adjustment Modal -->
|
|
<div class="modal fade" id="loyaltyModal" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 shadow-lg rounded-4">
|
|
<div class="modal-header border-0 pb-0 p-4">
|
|
<h5 class="modal-title fw-bold"><?= __('loyalty_points') ?? 'Loyalty Points' ?></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body p-4 text-center">
|
|
<h6 id="loyaltyCustomerName" class="fw-bold mb-1"></h6>
|
|
<div class="display-6 fw-bold text-primary mb-4" id="loyaltyCurrentPoints">0.00</div>
|
|
|
|
<form method="POST">
|
|
<input type="hidden" name="action" value="adjust_points">
|
|
<input type="hidden" name="id" id="loyaltyCustomerId">
|
|
<div class="row g-2 mb-3">
|
|
<div class="col-6">
|
|
<input type="radio" class="btn-check" name="type" id="typeAdd" value="add" checked>
|
|
<label class="btn btn-outline-success w-100 py-2 rounded-3" for="typeAdd"><?= __('add') ?></label>
|
|
</div>
|
|
<div class="col-6">
|
|
<input type="radio" class="btn-check" name="type" id="typeSubtract" value="subtract">
|
|
<label class="btn btn-outline-danger w-100 py-2 rounded-3" for="typeSubtract"><?= __('subtract') ?></label>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<input type="number" name="points" class="form-control form-control-lg text-center rounded-3" placeholder="0.00" step="0.01" required>
|
|
</div>
|
|
<div class="mb-4">
|
|
<input type="text" name="description" class="form-control rounded-3" placeholder="<?= __('description') ?>">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold rounded-4 shadow-sm"><?= __('confirm') ?></button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="deleteForm" method="POST" style="display: none;">
|
|
<input type="hidden" name="action" id="deleteAction">
|
|
<input type="hidden" name="id" id="deleteId">
|
|
</form>
|
|
|
|
<script>
|
|
async function translateField(sourceId, targetId, direction) {
|
|
const sourceEl = document.getElementById(sourceId);
|
|
const targetEl = document.getElementById(targetId);
|
|
const text = sourceEl.value.trim();
|
|
if (!text) return;
|
|
const btn = event.currentTarget;
|
|
const originalHtml = btn.innerHTML;
|
|
btn.disabled = true; btn.innerHTML = "<span class=\"spinner-border spinner-border-sm\"></span>";
|
|
try {
|
|
const formData = new FormData(); formData.append("text", text); formData.append("direction", direction);
|
|
const resp = await fetch("api/translate.php", { method: "POST", body: formData });
|
|
const data = await resp.json(); if (data.success) targetEl.value = data.translation;
|
|
} catch (e) { console.error(e); }
|
|
finally { btn.disabled = false; btn.innerHTML = originalHtml; }
|
|
}
|
|
|
|
function openCustomerModal(customer = null) {
|
|
const modal = new bootstrap.Modal(document.getElementById('customerModal'));
|
|
const form = document.getElementById('customerForm');
|
|
const label = document.getElementById('customerModalLabel');
|
|
const idInput = document.getElementById('customerId');
|
|
if (customer) {
|
|
label.innerText = "<?= __('edit') ?> <?= __('customer') ?>";
|
|
document.getElementById('customerAction').value = 'edit_customer';
|
|
idInput.value = customer.id;
|
|
document.getElementById('customerPhone').value = customer.phone;
|
|
document.getElementById('customerNameEn').value = customer.name_en;
|
|
document.getElementById('customerNameAr').value = customer.name_ar;
|
|
document.getElementById('customerEmail').value = customer.email;
|
|
document.getElementById('customerAddressEn').value = customer.address_en || '';
|
|
document.getElementById('customerAddressAr').value = customer.address_ar || '';
|
|
} else {
|
|
label.innerText = "<?= __('add_new_customer') ?>";
|
|
document.getElementById('customerAction').value = 'add_customer';
|
|
form.reset(); idInput.value = '';
|
|
}
|
|
modal.show();
|
|
}
|
|
|
|
function openLoyaltyModal(id, name, points) {
|
|
const modal = new bootstrap.Modal(document.getElementById('loyaltyModal'));
|
|
document.getElementById('loyaltyCustomerId').value = id;
|
|
document.getElementById('loyaltyCustomerName').innerText = name;
|
|
document.getElementById('loyaltyCurrentPoints').innerText = parseFloat(points).toFixed(2);
|
|
modal.show();
|
|
}
|
|
|
|
function confirmDelete(type, id) {
|
|
if (confirm("<?= __('are_you_sure') ?>")) {
|
|
document.getElementById('deleteAction').value = 'delete_' + type;
|
|
document.getElementById('deleteId').value = id;
|
|
document.getElementById('deleteForm').submit();
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>.pointer { cursor: pointer; }</style>
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|