mirror of
https://github.com/myronblair/parkerslingshotrentals
synced 2026-06-30 17:50:31 -05:00
Add customers CRUD section to admin panel
This commit is contained in:
+236
-2
@@ -241,6 +241,36 @@ if ($isAjax) {
|
||||
echo json_encode(['ok'=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'customer_save') {
|
||||
$cid = (int)($_POST['id'] ?? 0);
|
||||
$name = substr(trim($_POST['name'] ?? ''), 0, 150);
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$phone = substr(trim($_POST['phone'] ?? ''), 0, 30);
|
||||
$dob = preg_match('/^\d{4}-\d{2}-\d{2}$/', $_POST['dob'] ?? '') ? $_POST['dob'] : null;
|
||||
$addr = substr(trim($_POST['address'] ?? ''), 0, 500);
|
||||
$notes = substr(trim($_POST['notes'] ?? ''), 0, 1000);
|
||||
$active = (int)($_POST['is_active'] ?? 1);
|
||||
if (!$name || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
echo json_encode(['error' => 'Name and valid email are required']); exit;
|
||||
}
|
||||
if ($cid) {
|
||||
db()->prepare("UPDATE pcs_customers SET name=?,email=?,phone=?,dob=?,address=?,notes=?,is_active=? WHERE id=?")
|
||||
->execute([$name,$email,$phone,$dob,$addr,$notes,$active,$cid]);
|
||||
} else {
|
||||
db()->prepare("INSERT INTO pcs_customers (name,email,phone,dob,address,notes,is_active) VALUES (?,?,?,?,?,?,?)")
|
||||
->execute([$name,$email,$phone,$dob,$addr,$notes,1]);
|
||||
$cid = (int)db()->lastInsertId();
|
||||
}
|
||||
echo json_encode(['ok'=>true,'id'=>$cid]); exit;
|
||||
}
|
||||
|
||||
if ($action === 'customer_delete') {
|
||||
$cid = (int)($_POST['id'] ?? 0);
|
||||
if ($cid) db()->prepare("DELETE FROM pcs_customers WHERE id=?")->execute([$cid]);
|
||||
echo json_encode(['ok'=>true]); exit;
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -285,8 +315,13 @@ button:hover{background:#ea580c}
|
||||
<?php exit; }
|
||||
|
||||
// ── Dashboard data ─────────────────────────────────────────────────────────────
|
||||
$bookings = db()->query("SELECT * FROM bookings ORDER BY rental_date ASC, created_at DESC")->fetchAll();
|
||||
$blocked = db()->query("SELECT * FROM blocked_dates ORDER BY block_date ASC")->fetchAll();
|
||||
$bookings = db()->query("SELECT * FROM bookings ORDER BY rental_date ASC, created_at DESC")->fetchAll();
|
||||
$blocked = db()->query("SELECT * FROM blocked_dates ORDER BY block_date ASC")->fetchAll();
|
||||
$customers = db()->query("
|
||||
SELECT c.*,
|
||||
(SELECT COUNT(*) FROM bookings WHERE email=c.email) AS booking_count
|
||||
FROM pcs_customers c ORDER BY c.created_at DESC
|
||||
")->fetchAll();
|
||||
|
||||
$stats = db()->query("
|
||||
SELECT
|
||||
@@ -413,6 +448,21 @@ textarea.notes-ta:focus{border-color:#f97316}
|
||||
.del-btn{background:none;border:none;color:#dc2626;cursor:pointer;font-size:1rem;padding:0;line-height:1}
|
||||
|
||||
@media(max-width:700px){.main{padding:1rem}.stat-val{font-size:1.5rem}}
|
||||
|
||||
/* Customer card */
|
||||
.cust-search{border:1px solid #e5e7eb;border-radius:6px;padding:.45rem .75rem;font-size:.875rem;width:100%;max-width:280px}
|
||||
.cust-form{background:#f9fafb;border-top:1px solid #f3f4f6;padding:1.25rem 1.5rem;display:none}
|
||||
.cust-form.open{display:block}
|
||||
.cust-form .fg{display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-bottom:.75rem}
|
||||
@media(max-width:600px){.cust-form .fg{grid-template-columns:1fr}}
|
||||
.cust-form label{font-size:.78rem;color:#6b7280;display:block;margin-bottom:.2rem}
|
||||
.cust-form input,.cust-form textarea{width:100%;border:1px solid #e5e7eb;border-radius:6px;padding:.45rem .65rem;font-size:.875rem;font-family:inherit;outline:none}
|
||||
.cust-form input:focus,.cust-form textarea:focus{border-color:#f97316}
|
||||
.cust-form textarea{resize:vertical;min-height:60px}
|
||||
.cust-form .form-actions{display:flex;gap:.5rem;margin-top:.75rem}
|
||||
.cust-badge{display:inline-block;padding:.15rem .5rem;border-radius:999px;font-size:.7rem;font-weight:700}
|
||||
.cust-active{background:#dcfce7;color:#15803d}
|
||||
.cust-inactive{background:#f3f4f6;color:#9ca3af}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -784,6 +834,93 @@ textarea.notes-ta:focus{border-color:#f97316}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customers -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Customers</h2>
|
||||
<div style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap">
|
||||
<input class="cust-search" type="search" id="custSearch" placeholder="Search name, email, phone…" oninput="filterCustomers(this.value)">
|
||||
<button class="save-btn" onclick="toggleCustForm(0)" id="addCustBtn">+ Add Customer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit form -->
|
||||
<div class="cust-form" id="custForm">
|
||||
<input type="hidden" id="custId" value="0">
|
||||
<div class="fg">
|
||||
<div>
|
||||
<label>Full Name *</label>
|
||||
<input type="text" id="custName" placeholder="Jane Doe" maxlength="150">
|
||||
</div>
|
||||
<div>
|
||||
<label>Email *</label>
|
||||
<input type="email" id="custEmail" placeholder="jane@example.com">
|
||||
</div>
|
||||
<div>
|
||||
<label>Phone</label>
|
||||
<input type="tel" id="custPhone" placeholder="(817) 555-0100" maxlength="30">
|
||||
</div>
|
||||
<div>
|
||||
<label>Date of Birth</label>
|
||||
<input type="date" id="custDob">
|
||||
</div>
|
||||
<div style="grid-column:1/-1">
|
||||
<label>Address</label>
|
||||
<input type="text" id="custAddr" placeholder="123 Main St, Weatherford TX 76086" maxlength="500">
|
||||
</div>
|
||||
<div style="grid-column:1/-1">
|
||||
<label>Notes</label>
|
||||
<textarea id="custNotes" maxlength="1000" placeholder="e.g. Preferred customer, allergies, anything relevant…"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="save-btn" onclick="saveCustomer()">Save Customer</button>
|
||||
<button class="save-btn" onclick="toggleCustForm(0)" style="background:#6b7280">Cancel</button>
|
||||
</div>
|
||||
<div id="custFormMsg" style="margin-top:.5rem;font-size:.8rem;display:none"></div>
|
||||
</div>
|
||||
|
||||
<?php if (empty($customers)): ?>
|
||||
<div style="padding:3rem;text-align:center;color:#9ca3af">No customers yet.</div>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x:auto">
|
||||
<table id="custTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>DOB</th>
|
||||
<th>Bookings</th>
|
||||
<th>Status</th>
|
||||
<th>Added</th>
|
||||
<th style="width:80px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($customers as $c): ?>
|
||||
<tr class="cust-row" data-search="<?= strtolower(htmlspecialchars($c['name'].' '.$c['email'].' '.($c['phone']??''))) ?>">
|
||||
<td><strong><?= htmlspecialchars($c['name']) ?></strong></td>
|
||||
<td style="font-size:.82rem"><a href="mailto:<?= htmlspecialchars($c['email']) ?>" style="color:#f97316;text-decoration:none"><?= htmlspecialchars($c['email']) ?></a></td>
|
||||
<td style="font-size:.82rem"><?= htmlspecialchars($c['phone'] ?? '—') ?></td>
|
||||
<td style="font-size:.82rem"><?= $c['dob'] ? date('M j, Y', strtotime($c['dob'])) : '—' ?></td>
|
||||
<td style="text-align:center"><?= (int)$c['booking_count'] ?></td>
|
||||
<td><span class="cust-badge <?= $c['is_active']?'cust-active':'cust-inactive' ?>"><?= $c['is_active']?'Active':'Inactive' ?></span></td>
|
||||
<td style="font-size:.78rem;color:#9ca3af;white-space:nowrap"><?= date('M j, Y', strtotime($c['created_at'])) ?></td>
|
||||
<td style="white-space:nowrap">
|
||||
<button class="flow-toggle" style="font-size:.72rem;margin-right:4px"
|
||||
onclick="editCustomer(<?= htmlspecialchars(json_encode($c), ENT_QUOTES) ?>)">Edit</button>
|
||||
<button class="flow-toggle" style="font-size:.72rem;border-color:#dc2626;color:#dc2626"
|
||||
onclick="deleteCustomer(<?= $c['id'] ?>,this)">Del</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div><!-- /.main -->
|
||||
|
||||
<script>
|
||||
@@ -982,6 +1119,103 @@ function squareAction(id, action, btn) {
|
||||
});
|
||||
}
|
||||
|
||||
// ── Customer CRUD ─────────────────────────────────────────────────────────────
|
||||
function toggleCustForm(id) {
|
||||
const form = document.getElementById('custForm');
|
||||
const isOpen = form.classList.contains('open');
|
||||
if (isOpen && id === 0) {
|
||||
form.classList.remove('open');
|
||||
return;
|
||||
}
|
||||
if (!isOpen || id === 0) {
|
||||
// Reset form for new customer
|
||||
document.getElementById('custId').value = 0;
|
||||
document.getElementById('custName').value = '';
|
||||
document.getElementById('custEmail').value = '';
|
||||
document.getElementById('custPhone').value = '';
|
||||
document.getElementById('custDob').value = '';
|
||||
document.getElementById('custAddr').value = '';
|
||||
document.getElementById('custNotes').value = '';
|
||||
const msg = document.getElementById('custFormMsg');
|
||||
msg.style.display = 'none';
|
||||
document.getElementById('addCustBtn').textContent = '✕ Close';
|
||||
}
|
||||
form.classList.add('open');
|
||||
document.getElementById('custName').focus();
|
||||
}
|
||||
|
||||
function editCustomer(c) {
|
||||
document.getElementById('custId').value = c.id;
|
||||
document.getElementById('custName').value = c.name || '';
|
||||
document.getElementById('custEmail').value = c.email || '';
|
||||
document.getElementById('custPhone').value = c.phone || '';
|
||||
document.getElementById('custDob').value = c.dob || '';
|
||||
document.getElementById('custAddr').value = c.address || '';
|
||||
document.getElementById('custNotes').value = c.notes || '';
|
||||
const msg = document.getElementById('custFormMsg');
|
||||
msg.style.display = 'none';
|
||||
const form = document.getElementById('custForm');
|
||||
form.classList.add('open');
|
||||
document.getElementById('addCustBtn').textContent = '✕ Close';
|
||||
form.scrollIntoView({behavior:'smooth', block:'nearest'});
|
||||
}
|
||||
|
||||
function saveCustomer() {
|
||||
const id = document.getElementById('custId').value;
|
||||
const name = document.getElementById('custName').value.trim();
|
||||
const email = document.getElementById('custEmail').value.trim();
|
||||
const msg = document.getElementById('custFormMsg');
|
||||
if (!name || !email) { showCustMsg('Name and email are required.', 'red'); return; }
|
||||
const body = 'action=customer_save'
|
||||
+ '&id=' + encodeURIComponent(id)
|
||||
+ '&name=' + encodeURIComponent(name)
|
||||
+ '&email=' + encodeURIComponent(email)
|
||||
+ '&phone=' + encodeURIComponent(document.getElementById('custPhone').value)
|
||||
+ '&dob=' + encodeURIComponent(document.getElementById('custDob').value)
|
||||
+ '&address=' + encodeURIComponent(document.getElementById('custAddr').value)
|
||||
+ '¬es=' + encodeURIComponent(document.getElementById('custNotes').value)
|
||||
+ '&_t=' + ADMIN_TOKEN;
|
||||
fetch('/admin/', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/x-www-form-urlencoded','X-Requested-With':'XMLHttpRequest'},
|
||||
body
|
||||
}).then(r=>r.json()).then(d=>{
|
||||
if (d.ok) {
|
||||
showCustMsg(id === '0' ? 'Customer added.' : 'Saved.', '#16a34a');
|
||||
setTimeout(()=>location.reload(), 900);
|
||||
} else {
|
||||
showCustMsg(d.error || 'Error saving.', 'red');
|
||||
}
|
||||
}).catch(()=>showCustMsg('Request failed.', 'red'));
|
||||
}
|
||||
|
||||
function deleteCustomer(id, btn) {
|
||||
if (!confirm('Delete this customer? This cannot be undone.')) return;
|
||||
btn.disabled = true;
|
||||
fetch('/admin/', {
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/x-www-form-urlencoded','X-Requested-With':'XMLHttpRequest'},
|
||||
body:'action=customer_delete&id='+id+'&_t='+ADMIN_TOKEN
|
||||
}).then(r=>r.json()).then(d=>{
|
||||
if (d.ok) btn.closest('tr').remove();
|
||||
else { btn.disabled=false; alert(d.error||'Error deleting.'); }
|
||||
});
|
||||
}
|
||||
|
||||
function filterCustomers(q) {
|
||||
const term = q.toLowerCase().trim();
|
||||
document.querySelectorAll('.cust-row').forEach(row=>{
|
||||
row.style.display = (!term || row.dataset.search.includes(term)) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function showCustMsg(text, color) {
|
||||
const msg = document.getElementById('custFormMsg');
|
||||
msg.textContent = text;
|
||||
msg.style.color = color;
|
||||
msg.style.display = 'block';
|
||||
}
|
||||
|
||||
// ── Block / unblock dates ─────────────────────────────────────────────────────
|
||||
function blockDate(e) {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user