- initial

This commit is contained in:
Ty Clifford
2026-04-24 08:41:52 -04:00
commit b57091e4ad
49 changed files with 7371 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
<?php
// Admin header — included by all admin pages after setting $adminTitle
require_once dirname(__DIR__).'/includes/boot.php';
requireAdmin();
$pageTitle = ($adminTitle ?? 'Admin') . ' — Keyser WV Admin';
include dirname(__DIR__).'/includes/header.php';
?>
<div class="admin-wrap">
<aside class="admin-side">
<div class="admin-side-title">ADMIN PANEL</div>
<a class="asl" href="/admin/">📊 Dashboard</a>
<div class="admin-side-title" style="margin-top:1rem">DIRECTORY</div>
<a class="asl" href="/admin/businesses.php">🏢 Businesses</a>
<a class="asl" href="/admin/categories.php">🏷️ Categories</a>
<a class="asl" href="/admin/menus.php">🍽️ Menus</a>
<a class="asl" href="/admin/reviews.php">⭐ Reviews</a>
<a class="asl" href="/admin/alerts.php">📢 Alerts</a>
<div class="admin-side-title" style="margin-top:1rem">COMMUNITY</div>
<a class="asl" href="/admin/events.php">📅 Events</a>
<a class="asl" href="/admin/attractions.php">🗺️ Attractions</a>
<a class="asl" href="/admin/reports.php">🚩 Reports</a>
<div class="admin-side-title" style="margin-top:1rem">USERS</div>
<a class="asl" href="/admin/users.php">👥 Users</a>
<a class="asl" href="/admin/owners.php">🔑 Assign Owners</a>
<div class="admin-side-title" style="margin-top:1rem">SETTINGS</div>
<a class="asl" href="/admin/settings.php">⚙️ Site Settings</a>
<hr style="border-color:var(--bdr);margin:1rem 0">
<a class="asl" href="/index.php">🏠 View Site</a>
<a class="asl" href="/logout.php">🚪 Sign Out</a>
</aside>
<div class="admin-main">
<?php
// Helper: render admin section title
function adminTitle(string $t, string $sub=''): void {
echo '<div class="admin-sec-title">'.e($t).'</div>';
if($sub) echo '<p style="color:var(--text-m);font-size:.88rem;margin-top:-.5rem;margin-bottom:1rem">'.e($sub).'</p>';
}
+167
View File
@@ -0,0 +1,167 @@
<?php
$adminTitle = 'Businesses';
include __DIR__.'/admin_header.php';
// Handle POST actions
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'add' || $act === 'edit') {
$fields = [
'name' => ps('name'),
'slug' => ps('slug') ?: makeSlug(ps('name')),
'category_id' => (int)p('category_id'),
'subcategory' => ps('subcategory'),
'description' => ps('description'),
'address' => ps('address'),
'phone' => ps('phone'),
'email' => ps('email'),
'website' => ps('website'),
'lat' => (float)p('lat', 39.4364),
'lng' => (float)p('lng', -78.9762),
'is_active' => (int)p('is_active', 1),
'is_featured' => (int)p('is_featured', 0),
];
// Build hours JSON
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
$hrs = [];
foreach ($days as $d) { $v = ps("hours_$d"); if ($v) $hrs[$d] = $v; }
$fields['hours'] = json_encode($hrs);
if (!$fields['name']) { flash('Business name is required.','error'); go('/admin/businesses.php'); }
if ($act === 'add') {
// Ensure unique slug
$base = $fields['slug']; $i = 0;
while (qval("SELECT id FROM businesses WHERE slug=?",[$fields['slug'].($i?"-$i":"")])) $i++;
$fields['slug'] .= $i ? "-$i" : "";
qrun("INSERT INTO businesses(name,slug,category_id,subcategory,description,address,phone,email,website,hours,lat,lng,is_active,is_featured)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)",array_values($fields));
flash('Business added!','success');
} else {
$id = (int)p('id');
// Check slug uniqueness excluding self
$base = $fields['slug']; $i = 0;
while (qval("SELECT id FROM businesses WHERE slug=? AND id!=?",[$fields['slug'].($i?"-$i":""),$id])) $i++;
$fields['slug'] .= $i ? "-$i" : "";
qrun("UPDATE businesses SET name=?,slug=?,category_id=?,subcategory=?,description=?,address=?,phone=?,email=?,website=?,hours=?,lat=?,lng=?,is_active=?,is_featured=? WHERE id=?",array_merge(array_values($fields),[$id]));
flash('Business updated!','success');
}
go('/admin/businesses.php');
}
if ($act === 'delete') {
$id = (int)p('id');
qrun("DELETE FROM businesses WHERE id=?",[$id]);
flash('Business deleted.','success');
go('/admin/businesses.php');
}
if ($act === 'toggle') {
$id = (int)p('id');
qrun("UPDATE businesses SET is_active = 1 - is_active WHERE id=?",[$id]);
go('/admin/businesses.php');
}
}
$editId = iget('edit');
$editing = $editId ? qone("SELECT * FROM businesses WHERE id=?",[$editId]) : null;
$cats = qall("SELECT * FROM categories ORDER BY sort_order");
$q = gs('q'); $page = max(1,iget('page')); $perPage = 25;
$where = $q ? "WHERE b.name LIKE ? OR b.address LIKE ?" : "";
$params = $q ? ["%$q%","%$q%"] : [];
$total = (int)qval("SELECT COUNT(*) FROM businesses b $where",$params);
$pages = max(1,(int)ceil($total/$perPage));
$page = min($page,$pages);
$offset = ($page-1)*$perPage;
$bizs = qall("SELECT b.*,c.name cat FROM businesses b LEFT JOIN categories c ON c.id=b.category_id $where ORDER BY b.name LIMIT $perPage OFFSET $offset",$params);
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
?>
<?php adminTitle($editing ? 'Edit Business: '.e($editing['name']) : 'Manage Businesses') ?>
<!-- Add/Edit Form -->
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title"><?=$editing ? '✏️ EDIT: '.strtoupper(e($editing['name'])) : ' ADD NEW BUSINESS'?></div>
<?php $h = $editing ? parseHours($editing['hours']) : []; ?>
<form method="POST">
<?=csrfField()?>
<input type="hidden" name="_act" value="<?=$editing?'edit':'add'?>">
<?php if($editing): ?><input type="hidden" name="id" value="<?=$editing['id']?>"><?php endif; ?>
<div class="form-row">
<div class="fg"><label class="flabel">BUSINESS NAME *</label><input type="text" name="name" class="finput" value="<?=e($editing['name']??'')?>" required></div>
<div class="fg"><label class="flabel">SLUG (auto-generated)</label><input type="text" name="slug" class="finput" value="<?=e($editing['slug']??'')?>" placeholder="auto-generated from name"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">CATEGORY</label><select name="category_id" class="fselect"><?php foreach($cats as $c): ?><option value="<?=$c['id']?>"<?=($editing['category_id']??0)==$c['id']?' selected':''?>><?=e($c['icon'].' '.$c['name'])?></option><?php endforeach; ?></select></div>
<div class="fg"><label class="flabel">SUBCATEGORY</label><input type="text" name="subcategory" class="finput" value="<?=e($editing['subcategory']??'')?>" placeholder="e.g. Italian Restaurant"></div>
</div>
<div class="fg"><label class="flabel">DESCRIPTION</label><textarea name="description" class="ftextarea" style="min-height:90px"><?=e($editing['description']??'')?></textarea></div>
<div class="form-row">
<div class="fg"><label class="flabel">ADDRESS</label><input type="text" name="address" class="finput" value="<?=e($editing['address']??'')?>"></div>
<div class="fg"><label class="flabel">PHONE</label><input type="text" name="phone" class="finput" value="<?=e($editing['phone']??'')?>"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">EMAIL</label><input type="email" name="email" class="finput" value="<?=e($editing['email']??'')?>"></div>
<div class="fg"><label class="flabel">WEBSITE</label><input type="text" name="website" class="finput" value="<?=e($editing['website']??'')?>" placeholder="example.com (no https://)"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">LATITUDE</label><input type="number" name="lat" class="finput" value="<?=e($editing['lat']??39.4364)?>" step="0.0001"></div>
<div class="fg"><label class="flabel">LONGITUDE</label><input type="number" name="lng" class="finput" value="<?=e($editing['lng']??-78.9762)?>" step="0.0001"></div>
</div>
<div style="margin-bottom:1rem">
<label class="flabel" style="margin-bottom:.6rem">HOURS OF OPERATION</label>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:.5rem">
<?php foreach($days as $d): ?>
<div style="display:flex;gap:.4rem;align-items:center">
<span style="font-family:'Oswald',sans-serif;font-size:.72rem;color:var(--gold);width:28px;flex-shrink:0"><?=$d?></span>
<input type="text" name="hours_<?=$d?>" class="finput" style="font-size:.8rem;padding:.4rem .6rem" value="<?=e($h[$d]??'')?>" placeholder="e.g. 9am5pm">
</div>
<?php endforeach; ?>
</div>
</div>
<div class="form-row" style="margin-bottom:.75rem">
<div class="fg"><label class="flabel">STATUS</label><select name="is_active" class="fselect"><option value="1"<?=($editing['is_active']??1)?'selected':''?>>Active</option><option value="0"<?=isset($editing)&&!$editing['is_active']?' selected':''?>>Inactive (hidden)</option></select></div>
<div class="fg"><label class="flabel">FEATURED</label><select name="is_featured" class="fselect"><option value="0"<?=!($editing['is_featured']??0)?' selected':''?>>Not Featured</option><option value="1"<?=($editing['is_featured']??0)?' selected':''?>>★ Featured</option></select></div>
</div>
<div class="flex-row">
<button type="submit" class="btn btn-primary"><?=$editing?'Update Business':'Add Business'?></button>
<?php if($editing): ?><a href="/admin/businesses.php" class="btn btn-secondary">Cancel</a><?php endif; ?>
</div>
</form>
</div>
<!-- Businesses list -->
<div class="admin-sec-title">ALL BUSINESSES (<?=$total?>)</div>
<form method="GET" style="display:flex;gap:.5rem;margin-bottom:1rem">
<input type="text" name="q" class="finput" style="max-width:300px" value="<?=e($q)?>" placeholder="Search businesses…">
<button type="submit" class="btn btn-secondary btn-sm">Search</button>
<?php if($q): ?><a href="/admin/businesses.php" class="btn btn-outline btn-sm">Clear</a><?php endif; ?>
</form>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>NAME</th><th>CATEGORY</th><th>PHONE</th><th>STATUS</th><th>FEATURED</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($bizs as $b): ?>
<tr>
<td><a href="/business.php?slug=<?=e($b['slug'])?>" class="td-name" target="_blank"><?=e($b['name'])?></a></td>
<td><span style="font-size:.78rem;color:var(--text-d)"><?=e($b['cat']??'—')?></span></td>
<td style="font-size:.8rem"><?=e($b['phone'])?></td>
<td><?=$b['is_active']?'<span class="badge badge-green">Active</span>':'<span class="badge badge-gray">Inactive</span>'?></td>
<td><?=$b['is_featured']?'<span class="badge badge-gold">★</span>':'—'?></td>
<td>
<div class="flex-row" style="gap:.3rem">
<a href="/admin/businesses.php?edit=<?=$b['id']?>" class="btn btn-xs btn-outline">✏️ Edit</a>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="toggle"><input type="hidden" name="id" value="<?=$b['id']?>"><button class="btn btn-xs btn-secondary"><?=$b['is_active']?'Deactivate':'Activate'?></button></form>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$b['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Permanently delete '<?=e($b['name'])?>' and all its data? This cannot be undone.">🗑️ Delete</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if($pages>1): ?>
<div style="display:flex;gap:.5rem;margin-top:1rem;flex-wrap:wrap">
<?php for($i=1;$i<=$pages;$i++): ?><a href="?<?=http_build_query(array_filter(['q'=>$q,'page'=>$i]))?>" class="btn btn-sm<?=$i===$page?' btn-primary':' btn-outline'?>"><?=$i?></a><?php endfor; ?>
</div>
<?php endif; ?>
<?php include __DIR__.'/admin_footer.php'; ?>
+109
View File
@@ -0,0 +1,109 @@
<?php
$adminTitle = 'Dashboard';
include __DIR__.'/admin_header.php';
$stats = [
'Businesses' => qval("SELECT COUNT(*) FROM businesses WHERE is_active=1"),
'Users' => qval("SELECT COUNT(*) FROM users"),
'Reviews' => qval("SELECT COUNT(*) FROM reviews"),
'Events' => qval("SELECT COUNT(*) FROM events WHERE status='approved'"),
'Pending Events' => qval("SELECT COUNT(*) FROM events WHERE status='pending'"),
'Open Reports' => qval("SELECT COUNT(*) FROM reports WHERE status='open'"),
'Attractions' => qval("SELECT COUNT(*) FROM attractions WHERE is_active=1"),
'Active Alerts' => qval("SELECT COUNT(*) FROM alerts WHERE is_active=1"),
];
$recentBiz = qall("SELECT * FROM businesses ORDER BY created_at DESC LIMIT 5");
$recentUsers = qall("SELECT * FROM users ORDER BY created_at DESC LIMIT 5");
$pendingEvts = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.submitted_by WHERE e.status='pending' ORDER BY e.created_at DESC LIMIT 5");
$openReports = qall("SELECT r.*,b.name biz,u.username reporter FROM reports r JOIN businesses b ON b.id=r.business_id JOIN users u ON u.id=r.user_id WHERE r.status='open' ORDER BY r.created_at DESC LIMIT 5");
?>
<?php adminTitle('Site Overview','Welcome to the Discover Keyser WV administration panel.') ?>
<div class="stat-cards">
<?php foreach($stats as $lbl=>$val): ?>
<div class="stat-card"><div class="sc-n"><?=$val?></div><div class="sc-l"><?=e($lbl)?></div></div>
<?php endforeach; ?>
</div>
<div class="g2" style="gap:1.5rem;margin-bottom:2rem">
<?php if($pendingEvts): ?>
<div>
<div class="admin-sec-title">⏳ PENDING EVENTS (<?=count($pendingEvts)?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>EVENT</th><th>SUBMITTED BY</th><th>DATE</th><th></th></tr></thead>
<tbody>
<?php foreach($pendingEvts as $ev): ?>
<tr>
<td class="td-name"><?=e($ev['title'])?></td>
<td><?=e($ev['username']??'—')?></td>
<td><?=fdate($ev['event_date'])?></td>
<td><a href="/admin/events.php?edit=<?=$ev['id']?>" class="btn btn-xs btn-primary">Review</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php if($openReports): ?>
<div>
<div class="admin-sec-title">🚩 OPEN REPORTS (<?=count($openReports)?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>BUSINESS</th><th>TYPE</th><th>FROM</th><th></th></tr></thead>
<tbody>
<?php foreach($openReports as $r): ?>
<tr>
<td class="td-name"><?=e($r['biz'])?></td>
<td><span class="badge badge-gold"><?=e($r['type'])?></span></td>
<td><?=e($r['reporter'])?></td>
<td><a href="/admin/reports.php" class="btn btn-xs btn-outline">View</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
<div class="g2">
<div>
<div class="admin-sec-title">🏢 RECENT BUSINESSES</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>NAME</th><th>STATUS</th><th></th></tr></thead>
<tbody>
<?php foreach($recentBiz as $b): ?>
<tr>
<td class="td-name"><?=e($b['name'])?></td>
<td><?=$b['is_active']?'<span class="badge badge-green">Active</span>':'<span class="badge badge-gray">Inactive</span>'?></td>
<td><a href="/admin/businesses.php?edit=<?=$b['id']?>" class="btn btn-xs btn-outline">Edit</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<a href="/admin/businesses.php" class="btn btn-outline btn-sm" style="margin-top:.75rem">Manage All Businesses</a>
</div>
<div>
<div class="admin-sec-title">👥 RECENT USERS</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>USERNAME</th><th>JOINED</th><th></th></tr></thead>
<tbody>
<?php foreach($recentUsers as $u): ?>
<tr>
<td class="td-name"><?=e($u['username'])?><?=$u['is_admin']?' <span class="badge badge-gold">ADMIN</span>':''?></td>
<td><?=fdate($u['created_at'])?></td>
<td><a href="/admin/users.php?edit=<?=$u['id']?>" class="btn btn-xs btn-outline">Edit</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<a href="/admin/users.php" class="btn btn-outline btn-sm" style="margin-top:.75rem">Manage All Users</a>
</div>
</div>
<?php include __DIR__.'/admin_footer.php'; ?>
+3
View File
@@ -0,0 +1,3 @@
</div><!-- /.admin-main -->
</div><!-- /.admin-wrap -->
<?php include dirname(__DIR__).'/includes/footer.php'; ?>
+44
View File
@@ -0,0 +1,44 @@
<?php
// Admin header — included by all admin pages after setting $adminTitle
require_once dirname(__DIR__).'/includes/boot.php';
requireAdmin();
$pageTitle = ($adminTitle ?? 'Admin') . ' — Keyser WV Admin';
include dirname(__DIR__).'/includes/header.php';
// Pending owner edit count for badge
$_pendingEdits = (int)qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'");
?>
<div class="admin-wrap">
<aside class="admin-side">
<div class="admin-side-title">ADMIN PANEL</div>
<a class="asl" href="/admin/">📊 Dashboard</a>
<div class="admin-side-title" style="margin-top:1rem">DIRECTORY</div>
<a class="asl" href="/admin/businesses.php">🏢 Businesses</a>
<a class="asl" href="/admin/categories.php">🏷️ Categories</a>
<a class="asl" href="/admin/menus.php">🍽️ Menus</a>
<a class="asl" href="/admin/reviews.php">⭐ Reviews</a>
<a class="asl" href="/admin/alerts.php">📢 Alerts</a>
<a class="asl" href="/admin/biz_edits.php" style="display:flex;align-items:center;justify-content:space-between">
<span>✏️ Owner Edits</span>
<?php if ($_pendingEdits): ?>
<span style="background:var(--gold);color:var(--bg0);font-family:'Oswald',sans-serif;font-size:.65rem;padding:.1rem .42rem;border-radius:10px;font-weight:600"><?=$_pendingEdits?></span>
<?php endif; ?>
</a>
<div class="admin-side-title" style="margin-top:1rem">COMMUNITY</div>
<a class="asl" href="/admin/events.php">📅 Events</a>
<a class="asl" href="/admin/attractions.php">🗺️ Attractions</a>
<a class="asl" href="/admin/reports.php">🚩 Reports</a>
<div class="admin-side-title" style="margin-top:1rem">USERS</div>
<a class="asl" href="/admin/users.php">👥 Users</a>
<a class="asl" href="/admin/owners.php">🔑 Assign Owners</a>
<div class="admin-side-title" style="margin-top:1rem">SETTINGS</div>
<a class="asl" href="/admin/settings.php">⚙️ Site Settings</a>
<hr style="border-color:var(--bdr);margin:1rem 0">
<a class="asl" href="/index.php">🏠 View Site</a>
<a class="asl" href="/logout.php">🚪 Sign Out</a>
</aside>
<div class="admin-main">
<?php
function adminTitle(string $t, string $sub=''): void {
echo '<div class="admin-sec-title">'.e($t).'</div>';
if($sub) echo '<p style="color:var(--text-m);font-size:.88rem;margin-top:-.5rem;margin-bottom:1rem">'.e($sub).'</p>';
}
+59
View File
@@ -0,0 +1,59 @@
<?php
$adminTitle = 'Business Alerts';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'add') {
$bid=(int)p('business_id');$title=ps('title');$body=ps('body');$type=p('type','info');
if($bid&&$title) qrun("INSERT INTO alerts(business_id,type,title,body)VALUES(?,?,?,?)",[$bid,$type,$title,$body]);
flash('Alert posted!','success');
}
if ($act === 'toggle') { qrun("UPDATE alerts SET is_active=1-is_active WHERE id=?",[(int)p('id')]); }
if ($act === 'delete') { qrun("DELETE FROM alerts WHERE id=?",[(int)p('id')]); flash('Alert deleted.','success'); }
go('/admin/alerts.php');
}
$bizs = qall("SELECT id,name FROM businesses WHERE is_active=1 ORDER BY name");
$alerts = qall("SELECT a.*,b.name biz FROM alerts a JOIN businesses b ON b.id=a.business_id ORDER BY a.created_at DESC");
?>
<?php adminTitle('Business Alerts & Announcements') ?>
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title"> POST NEW ALERT</div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="add">
<div class="form-row">
<div class="fg"><label class="flabel">BUSINESS</label><select name="business_id" class="fselect" required><option value="">— Select —</option><?php foreach($bizs as $b): ?><option value="<?=$b['id']?>"><?=e($b['name'])?></option><?php endforeach; ?></select></div>
<div class="fg"><label class="flabel">TYPE</label><select name="type" class="fselect"><option value="info">️ Info</option><option value="news">📰 News</option><option value="event">🎉 Event</option><option value="warning">⚠️ Warning</option><option value="sale">🏷️ Sale/Promo</option></select></div>
</div>
<div class="fg"><label class="flabel">TITLE *</label><input type="text" name="title" class="finput" required></div>
<div class="fg"><label class="flabel">BODY</label><textarea name="body" class="ftextarea" style="min-height:65px"></textarea></div>
<button type="submit" class="btn btn-primary btn-sm">Post Alert</button>
</form>
</div>
<div class="admin-sec-title">ALL ALERTS (<?=count($alerts)?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>BUSINESS</th><th>TYPE</th><th>TITLE</th><th>STATUS</th><th>POSTED</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($alerts as $a): ?>
<tr>
<td class="td-name"><?=e($a['biz'])?></td>
<td><span style="font-size:1rem"><?=alertIcon($a['type'])?></span> <?=e($a['type'])?></td>
<td style="font-size:.85rem;max-width:220px"><?=e($a['title'])?></td>
<td><?=$a['is_active']?'<span class="badge badge-green">Active</span>':'<span class="badge badge-gray">Inactive</span>'?></td>
<td style="font-size:.8rem"><?=ago($a['created_at'])?></td>
<td>
<div class="flex-row" style="gap:.3rem">
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="toggle"><input type="hidden" name="id" value="<?=$a['id']?>"><button class="btn btn-xs btn-secondary"><?=$a['is_active']?'Disable':'Enable'?></button></form>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$a['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete alert?">🗑️</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include __DIR__.'/admin_footer.php'; ?>
+71
View File
@@ -0,0 +1,71 @@
<?php
$adminTitle = 'Attractions';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
$f = ['name'=>ps('name'),'category'=>ps('category'),'description'=>ps('description'),
'address'=>ps('address'),'phone'=>ps('phone'),'website'=>ps('website'),
'hours'=>ps('hours'),'is_active'=>(int)p('is_active',1),'sort_order'=>(int)p('sort_order',0)];
if ($act === 'add') { if($f['name']) qrun("INSERT INTO attractions(name,category,description,address,phone,website,hours,is_active,sort_order)VALUES(?,?,?,?,?,?,?,?,?)",array_values($f)); flash('Attraction added!','success'); }
if ($act === 'edit') { $id=(int)p('id'); if($f['name']) qrun("UPDATE attractions SET name=?,category=?,description=?,address=?,phone=?,website=?,hours=?,is_active=?,sort_order=? WHERE id=?",array_merge(array_values($f),[$id])); flash('Attraction updated!','success'); }
if ($act === 'delete') { qrun("DELETE FROM attractions WHERE id=?",[(int)p('id')]); flash('Attraction deleted.','success'); }
go('/admin/attractions.php');
}
$editId = iget('edit');
$editing = $editId ? qone("SELECT * FROM attractions WHERE id=?",[$editId]) : null;
$attrs = qall("SELECT * FROM attractions ORDER BY sort_order,name");
?>
<?php adminTitle('Manage Attractions') ?>
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title"><?=$editing?'✏️ EDIT: '.strtoupper(e($editing['name'])):' ADD ATTRACTION'?></div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="<?=$editing?'edit':'add'?>">
<?php if($editing): ?><input type="hidden" name="id" value="<?=$editing['id']?>"><?php endif; ?>
<div class="form-row">
<div class="fg"><label class="flabel">NAME *</label><input type="text" name="name" class="finput" value="<?=e($editing['name']??'')?>" required></div>
<div class="fg"><label class="flabel">CATEGORY</label><input type="text" name="category" class="finput" value="<?=e($editing['category']??'Sightseeing')?>" placeholder="e.g. Natural Landmark, Historic Site"></div>
</div>
<div class="fg"><label class="flabel">DESCRIPTION</label><textarea name="description" class="ftextarea" style="min-height:80px"><?=e($editing['description']??'')?></textarea></div>
<div class="form-row">
<div class="fg"><label class="flabel">ADDRESS</label><input type="text" name="address" class="finput" value="<?=e($editing['address']??'')?>"></div>
<div class="fg"><label class="flabel">PHONE</label><input type="text" name="phone" class="finput" value="<?=e($editing['phone']??'')?>"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">WEBSITE</label><input type="text" name="website" class="finput" value="<?=e($editing['website']??'')?>"></div>
<div class="fg"><label class="flabel">HOURS</label><input type="text" name="hours" class="finput" value="<?=e($editing['hours']??'')?>" placeholder="e.g. Open year-round, Dawn to Dusk"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">STATUS</label><select name="is_active" class="fselect"><option value="1"<?=($editing['is_active']??1)?' selected':''?>>Active</option><option value="0"<?=isset($editing)&&!$editing['is_active']?' selected':''?>>Hidden</option></select></div>
<div class="fg"><label class="flabel">SORT ORDER</label><input type="number" name="sort_order" class="finput" value="<?=$editing['sort_order']??0?>"></div>
</div>
<div class="flex-row">
<button type="submit" class="btn btn-primary"><?=$editing?'Update':'Add Attraction'?></button>
<?php if($editing): ?><a href="/admin/attractions.php" class="btn btn-secondary">Cancel</a><?php endif; ?>
</div>
</form>
</div>
<div class="admin-sec-title">ALL ATTRACTIONS (<?=count($attrs)?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>NAME</th><th>CATEGORY</th><th>STATUS</th><th>ORDER</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($attrs as $a): ?>
<tr>
<td class="td-name"><?=e($a['name'])?></td>
<td><span style="font-size:.78rem;color:var(--text-d)"><?=e($a['category'])?></span></td>
<td><?=$a['is_active']?'<span class="badge badge-green">Active</span>':'<span class="badge badge-gray">Hidden</span>'?></td>
<td><?=$a['sort_order']?></td>
<td><div class="flex-row" style="gap:.3rem">
<a href="/admin/attractions.php?edit=<?=$a['id']?>" class="btn btn-xs btn-outline">✏️ Edit</a>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$a['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete attraction?">🗑️</button></form>
</div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include __DIR__.'/admin_footer.php'; ?>
+117
View File
@@ -0,0 +1,117 @@
<?php
/**
* Business DB Diagnostic — shows exact queries and results
* Access: /admin/biz_debug.php
* Remove this file from production after use.
*/
require_once dirname(__DIR__).'/includes/boot.php';
requireAdmin();
header('Content-Type: text/plain; charset=utf-8');
$db = db();
echo "=== DISCOVER KEYSER WV — BUSINESS TABLE DIAGNOSTIC ===\n";
echo "Generated: ".date('Y-m-d H:i:s')."\n";
echo "DB File: ".DB_FILE."\n";
echo "DB Writable: ".(is_writable(DB_FILE)?'YES':'NO')."\n\n";
// 1. Schema
echo "--- SCHEMA (businesses table) ---\n";
$schema = $db->query("SELECT sql FROM sqlite_master WHERE type='table' AND name='businesses'")->fetchColumn();
echo $schema."\n\n";
// 2. Row count
$count = $db->query("SELECT COUNT(*) FROM businesses")->fetchColumn();
echo "--- ROW COUNT ---\n";
echo "SELECT COUNT(*) FROM businesses;\n=> $count rows\n\n";
// 3. All businesses (id, name, slug, is_active)
echo "--- ALL BUSINESSES ---\n";
echo "SELECT id, name, slug, category_id, is_active, is_featured FROM businesses ORDER BY id;\n";
$rows = $db->query("SELECT id, name, slug, category_id, is_active, is_featured FROM businesses ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $r) {
echo sprintf(" [%3d] %-40s slug=%-35s cat=%s active=%d featured=%d\n",
$r['id'], substr($r['name'],0,40), substr($r['slug'],0,35), $r['category_id'], $r['is_active'], $r['is_featured']);
}
echo "\n";
// 4. Test UPDATE with id=1
$id = isset($_GET['test_id']) ? (int)$_GET['test_id'] : 1;
$biz = $db->prepare("SELECT * FROM businesses WHERE id=?");
$biz->execute([$id]);
$row = $biz->fetch(PDO::FETCH_ASSOC);
echo "--- FETCH SINGLE ROW (id=$id) ---\n";
echo "SELECT * FROM businesses WHERE id=$id;\n";
if ($row) {
foreach ($row as $k => $v) {
echo sprintf(" %-14s = %s\n", $k, strlen($v) > 80 ? substr($v,0,80).'...' : $v);
}
} else {
echo " [NO ROW FOUND for id=$id]\n";
}
echo "\n";
// 5. Categories
echo "--- CATEGORIES ---\n";
echo "SELECT id, name, icon, sort_order FROM categories ORDER BY sort_order;\n";
$cats = $db->query("SELECT id, name, icon, sort_order FROM categories ORDER BY sort_order")->fetchAll(PDO::FETCH_ASSOC);
foreach ($cats as $c) {
echo sprintf(" [%2d] %s %s (sort=%d)\n", $c['id'], $c['icon'], $c['name'], $c['sort_order']);
}
echo "\n";
// 6. Test UPDATE statement (dry run — shows exact SQL and params)
if ($row) {
echo "--- UPDATE STATEMENT THAT WOULD BE RUN ---\n";
$sql = "UPDATE businesses SET name=?,slug=?,category_id=?,subcategory=?,description=?,address=?,phone=?,email=?,website=?,hours=?,lat=?,lng=?,is_active=?,is_featured=? WHERE id=?";
echo $sql."\n";
echo "Params (15 total):\n";
$params = [
$row['name'], $row['slug'], $row['category_id'], $row['subcategory'],
$row['description'], $row['address'], $row['phone'], $row['email'],
$row['website'], $row['hours'], $row['lat'], $row['lng'],
$row['is_active'], $row['is_featured'], $id
];
foreach ($params as $i => $v) {
echo sprintf(" [%2d] %s\n", $i+1, strlen((string)$v) > 80 ? substr($v,0,80).'...' : $v);
}
echo "\n";
// Actually execute the no-op UPDATE to verify it works
echo "--- EXECUTING NO-OP UPDATE (same values back) ---\n";
try {
$stmt = $db->prepare($sql);
$stmt->execute($params);
$affected = $stmt->rowCount();
echo "SUCCESS: $affected row(s) affected\n\n";
} catch (\Exception $ex) {
echo "ERROR: ".$ex->getMessage()."\n\n";
}
}
// 7. Duplicate slug check
echo "--- DUPLICATE SLUG CHECK ---\n";
echo "SELECT slug, COUNT(*) c FROM businesses GROUP BY slug HAVING c > 1;\n";
$dupes = $db->query("SELECT slug, COUNT(*) c FROM businesses GROUP BY slug HAVING c > 1")->fetchAll(PDO::FETCH_ASSOC);
if ($dupes) {
foreach ($dupes as $d) echo " DUPLICATE: {$d['slug']} ({$d['c']} times)\n";
} else {
echo " No duplicates found.\n";
}
echo "\n";
// 8. NULL/empty slug check
echo "--- NULL OR EMPTY SLUG CHECK ---\n";
echo "SELECT id, name, slug FROM businesses WHERE slug IS NULL OR slug='';\n";
$bad = $db->query("SELECT id, name, slug FROM businesses WHERE slug IS NULL OR slug=''")->fetchAll(PDO::FETCH_ASSOC);
if ($bad) {
foreach ($bad as $b) echo " BAD: [{$b['id']}] {$b['name']} slug=".var_export($b['slug'],true)."\n";
} else {
echo " All slugs OK.\n";
}
echo "\n";
echo "=== END DIAGNOSTIC ===\n";
echo "Tip: Add ?test_id=N to test a specific business by ID.\n";
+254
View File
@@ -0,0 +1,254 @@
<?php
/**
* Dedicated Business Editor — standalone, no dependency on businesses.php
* URL: /admin/biz_edit.php?id=N
*/
$adminTitle = 'Direct Business Editor';
include __DIR__.'/admin_header.php';
$id = iget('id');
$msg = '';
$err = '';
// ── Handle save ──────────────────────────────────────────
if (isPost() && p('_act') === 'save') {
csrfCheck();
$id = (int)p('id');
if (!$id) { $err = 'No business ID provided.'; goto render; }
$name = ps('name');
if (!$name) { $err = 'Business name is required.'; goto render; }
// Slug: use submitted or regenerate, ensure unique excluding self
$baseSlug = ps('slug') ?: makeSlug($name);
$slug = $baseSlug;
$i = 0;
while (true) {
$candidate = $baseSlug . ($i ? "-$i" : '');
$clash = qval("SELECT id FROM businesses WHERE slug=? AND id!=?", [$candidate, $id]);
if (!$clash) { $slug = $candidate; break; }
$i++;
}
// Hours
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
$hrs = [];
foreach ($days as $d) {
$v = ps("h_$d");
if ($v !== '') $hrs[$d] = $v;
}
// Run individual named UPDATEs so a failure on one field is visible
$updates = [
'name' => ps('name'),
'slug' => $slug,
'category_id' => (int)p('category_id'),
'subcategory' => ps('subcategory'),
'description' => ps('description'),
'address' => ps('address'),
'phone' => ps('phone'),
'email' => ps('email'),
'website' => ps('website'),
'hours' => json_encode($hrs),
'lat' => (float)p('lat', 39.4364),
'lng' => (float)p('lng', -78.9762),
'is_active' => (int)p('is_active', 1),
'is_featured' => (int)p('is_featured', 0),
];
try {
$setClauses = implode(', ', array_map(fn($k) => "$k=?", array_keys($updates)));
$sql = "UPDATE businesses SET $setClauses WHERE id=?";
$params = array_merge(array_values($updates), [$id]);
$stmt = db()->prepare($sql);
$stmt->execute($params);
$affected = $stmt->rowCount();
$msg = "Saved successfully ($affected row updated). SQL: $sql";
} catch (\Throwable $ex) {
$err = 'DB ERROR: '.$ex->getMessage();
}
}
render:
// ── Load business ────────────────────────────────────────
$biz = $id ? qone("SELECT * FROM businesses WHERE id=?", [$id]) : null;
$cats = qall("SELECT * FROM categories ORDER BY sort_order");
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
$h = $biz ? parseHours($biz['hours']) : [];
// All businesses for the picker
$all = qall("SELECT id, name FROM businesses ORDER BY name");
?>
<?php adminTitle('Direct Business Editor', 'Standalone editor — bypasses the main businesses page entirely.') ?>
<!-- Business picker -->
<div class="ibox" style="margin-bottom:1.5rem;max-width:500px">
<div class="ibox-title">SELECT BUSINESS TO EDIT</div>
<form method="GET" action="/admin/biz_edit.php" style="display:flex;gap:.6rem">
<select name="id" class="fselect" style="flex:1">
<option value="">-- choose --</option>
<?php foreach ($all as $b): ?>
<option value="<?=$b['id']?>"<?= $id === $b['id'] ? ' selected' : '' ?>><?=e($b['name'])?> (ID <?=$b['id']?>)</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-primary btn-sm">Load</button>
</form>
<?php if ($all): ?>
<p style="font-size:.78rem;color:var(--text-d);margin-top:.5rem">Or go directly: /admin/biz_edit.php?id=N</p>
<?php endif; ?>
</div>
<!-- Status messages -->
<?php if ($msg): ?>
<div style="background:rgba(42,122,71,.25);border:1px solid var(--green-l);color:#86e8a8;padding:.85rem 1.1rem;border-radius:6px;margin-bottom:1rem;font-size:.88rem;font-family:monospace;white-space:pre-wrap;word-break:break-all"><?=e($msg)?></div>
<?php endif; ?>
<?php if ($err): ?>
<div style="background:rgba(143,45,45,.3);border:1px solid var(--red-l);color:#ff9a9a;padding:.85rem 1.1rem;border-radius:6px;margin-bottom:1rem;font-size:.88rem;font-family:monospace;white-space:pre-wrap;word-break:break-all"><?=e($err)?></div>
<?php endif; ?>
<?php if ($biz): ?>
<!-- ── Edit form ─────────────────────────────────────────── -->
<div class="ibox" style="border-color:var(--gold-d)">
<div class="ibox-title">EDITING: <?=e($biz['name'])?> &nbsp;<span style="font-weight:400;color:var(--text-d)">(ID <?=$biz['id']?>)</span></div>
<form method="POST" action="/admin/biz_edit.php?id=<?=$biz['id']?>">
<?=csrfField()?>
<input type="hidden" name="_act" value="save">
<input type="hidden" name="id" value="<?=$biz['id']?>">
<!-- Name & Slug -->
<div class="form-row">
<div class="fg">
<label class="flabel">NAME *</label>
<input type="text" name="name" class="finput" value="<?=e($biz['name'])?>" required>
</div>
<div class="fg">
<label class="flabel">SLUG</label>
<input type="text" name="slug" class="finput" value="<?=e($biz['slug'])?>">
</div>
</div>
<!-- Category & Subcategory -->
<div class="form-row">
<div class="fg">
<label class="flabel">CATEGORY</label>
<select name="category_id" class="fselect">
<?php foreach ($cats as $c): ?>
<option value="<?=$c['id']?>"<?= (int)$biz['category_id'] === (int)$c['id'] ? ' selected' : '' ?>><?=e($c['icon'].' '.$c['name'])?></option>
<?php endforeach; ?>
</select>
</div>
<div class="fg">
<label class="flabel">SUBCATEGORY</label>
<input type="text" name="subcategory" class="finput" value="<?=e($biz['subcategory'])?>">
</div>
</div>
<!-- Description -->
<div class="fg">
<label class="flabel">DESCRIPTION</label>
<textarea name="description" class="ftextarea" style="min-height:100px"><?=e($biz['description'])?></textarea>
</div>
<!-- Address & Phone -->
<div class="form-row">
<div class="fg">
<label class="flabel">ADDRESS</label>
<input type="text" name="address" class="finput" value="<?=e($biz['address'])?>">
</div>
<div class="fg">
<label class="flabel">PHONE</label>
<input type="text" name="phone" class="finput" value="<?=e($biz['phone'])?>">
</div>
</div>
<!-- Email & Website -->
<div class="form-row">
<div class="fg">
<label class="flabel">EMAIL</label>
<input type="email" name="email" class="finput" value="<?=e($biz['email'])?>">
</div>
<div class="fg">
<label class="flabel">WEBSITE (no https://)</label>
<input type="text" name="website" class="finput" value="<?=e($biz['website'])?>">
</div>
</div>
<!-- Lat & Lng -->
<div class="form-row">
<div class="fg">
<label class="flabel">LATITUDE</label>
<input type="number" name="lat" class="finput" value="<?=e($biz['lat'])?>" step="0.0001">
</div>
<div class="fg">
<label class="flabel">LONGITUDE</label>
<input type="number" name="lng" class="finput" value="<?=e($biz['lng'])?>" step="0.0001">
</div>
</div>
<!-- Hours -->
<div style="margin-bottom:1rem">
<label class="flabel" style="margin-bottom:.6rem">HOURS OF OPERATION</label>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:.45rem">
<?php foreach ($days as $d): ?>
<div style="display:flex;gap:.4rem;align-items:center">
<span style="font-family:'Oswald',sans-serif;font-size:.72rem;color:var(--gold);width:30px;flex-shrink:0"><?=$d?></span>
<input type="text" name="h_<?=$d?>" class="finput" style="font-size:.8rem;padding:.38rem .6rem"
value="<?=e($h[$d] ?? '')?>" placeholder="e.g. 9am-5pm">
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Status & Featured -->
<div class="form-row" style="margin-bottom:.85rem">
<div class="fg">
<label class="flabel">STATUS</label>
<select name="is_active" class="fselect">
<option value="1"<?= $biz['is_active'] == 1 ? ' selected' : '' ?>>Active (visible)</option>
<option value="0"<?= $biz['is_active'] == 0 ? ' selected' : '' ?>>Inactive (hidden)</option>
</select>
</div>
<div class="fg">
<label class="flabel">FEATURED</label>
<select name="is_featured" class="fselect">
<option value="0"<?= $biz['is_featured'] == 0 ? ' selected' : '' ?>>Not Featured</option>
<option value="1"<?= $biz['is_featured'] == 1 ? ' selected' : '' ?>>Featured</option>
</select>
</div>
</div>
<div class="flex-row">
<button type="submit" class="btn btn-primary">Save Business</button>
<a href="/business.php?slug=<?=e($biz['slug'])?>" target="_blank" class="btn btn-outline btn-sm">View Listing</a>
<a href="/admin/biz_edit.php" class="btn btn-secondary btn-sm">Pick Different</a>
</div>
</form>
</div>
<!-- Raw data panel -->
<details style="margin-top:1rem">
<summary style="cursor:pointer;font-size:.82rem;color:var(--text-d);font-family:'Oswald',sans-serif;letter-spacing:.08em">🔍 RAW DATABASE VALUES FOR THIS ROW</summary>
<div style="background:var(--bg0);border:1px solid var(--bdr);border-radius:6px;padding:1rem;margin-top:.6rem;font-family:monospace;font-size:.78rem;color:var(--text-m)">
<?php foreach ($biz as $col => $val): ?>
<div style="display:flex;gap:.75rem;padding:.22rem 0;border-bottom:1px solid rgba(255,255,255,.04)">
<span style="color:var(--gold);width:120px;flex-shrink:0"><?=e($col)?></span>
<span style="word-break:break-all"><?=e(strlen((string)$val) > 200 ? substr($val,0,200).'...' : $val)?></span>
</div>
<?php endforeach; ?>
</div>
</details>
<?php elseif ($id): ?>
<div class="err-box">No business found with ID <?=(int)$id?>.</div>
<?php else: ?>
<div class="empty" style="padding:2rem">
<div class="empty-ico">🏢</div>
<h3>Select a business above to begin editing</h3>
</div>
<?php endif; ?>
<?php include __DIR__.'/admin_footer.php'; ?>
+336
View File
@@ -0,0 +1,336 @@
<?php
$adminTitle = 'Owner Edit Requests';
include __DIR__.'/admin_header.php';
// ── Field labels & apply logic ────────────────────────────
$fieldLabels = [
'description' => 'Description',
'address' => 'Address',
'phone' => 'Phone',
'email' => 'Email',
'hours' => 'Hours of Operation',
'is_active' => 'Listing Status (Open/Closed)',
];
// ── POST: approve / reject individual or all ─────────────
if (isPost()) {
csrfCheck();
$act = p('_act');
// Approve single field change
if ($act === 'approve') {
$reqId = (int)p('req_id');
$req = qone("SELECT * FROM business_edit_requests WHERE id=?", [$reqId]);
if ($req && $req['status'] === 'pending') {
$field = $req['field'];
$val = $req['new_value'];
// Validate field is one we allow
if (array_key_exists($field, $fieldLabels)) {
if ($field === 'is_active') {
qrun("UPDATE businesses SET is_active=? WHERE id=?", [(int)$val, $req['business_id']]);
} elseif ($field === 'hours') {
// Validate JSON before applying
$decoded = json_decode($val, true);
if (is_array($decoded)) {
qrun("UPDATE businesses SET hours=? WHERE id=?", [$val, $req['business_id']]);
}
} else {
qrun("UPDATE businesses SET $field=? WHERE id=?", [$val, $req['business_id']]);
}
qrun("UPDATE business_edit_requests SET status='approved', reviewed_at=datetime('now') WHERE id=?", [$reqId]);
flash('Change approved and applied.', 'success');
}
}
go('/admin/biz_edits.php'.(p('biz_id') ? '?biz_id='.p('biz_id') : ''));
}
// Reject single field change
if ($act === 'reject') {
$reqId = (int)p('req_id');
$notes = ps('admin_notes');
qrun("UPDATE business_edit_requests SET status='rejected', admin_notes=?, reviewed_at=datetime('now') WHERE id=?",
[$notes, $reqId]);
flash('Change rejected.', 'info');
go('/admin/biz_edits.php'.(p('biz_id') ? '?biz_id='.p('biz_id') : ''));
}
// Approve ALL pending for a business
if ($act === 'approve_all') {
$bizId = (int)p('biz_id');
$reqs = qall("SELECT * FROM business_edit_requests WHERE business_id=? AND status='pending'", [$bizId]);
$count = 0;
foreach ($reqs as $req) {
$field = $req['field'];
$val = $req['new_value'];
if (!array_key_exists($field, $fieldLabels)) continue;
if ($field === 'is_active') {
qrun("UPDATE businesses SET is_active=? WHERE id=?", [(int)$val, $bizId]);
} elseif ($field === 'hours') {
$decoded = json_decode($val, true);
if (is_array($decoded)) {
qrun("UPDATE businesses SET hours=? WHERE id=?", [$val, $bizId]);
}
} else {
qrun("UPDATE businesses SET $field=? WHERE id=?", [$val, $bizId]);
}
qrun("UPDATE business_edit_requests SET status='approved', reviewed_at=datetime('now') WHERE id=?", [$req['id']]);
$count++;
}
flash("All $count pending change".($count!=1?'s':'')." approved and applied.", 'success');
go('/admin/biz_edits.php?biz_id='.$bizId);
}
// Reject ALL pending for a business
if ($act === 'reject_all') {
$bizId = (int)p('biz_id');
$notes = ps('admin_notes');
qrun("UPDATE business_edit_requests SET status='rejected', admin_notes=?, reviewed_at=datetime('now')
WHERE business_id=? AND status='pending'",
[$notes, $bizId]);
flash('All pending changes rejected.', 'info');
go('/admin/biz_edits.php?biz_id='.$bizId);
}
}
// ── Filter by business ───────────────────────────────────
$bizId = iget('biz_id');
$filterStatus = gs('status') ?: 'pending';
// Grouped: businesses that have edit requests
$bizWithEdits = qall("
SELECT b.id, b.name, b.slug,
COUNT(CASE WHEN ber.status='pending' THEN 1 END) AS pending_count,
COUNT(CASE WHEN ber.status='approved' THEN 1 END) AS approved_count,
COUNT(CASE WHEN ber.status='rejected' THEN 1 END) AS rejected_count,
MAX(ber.created_at) AS latest_request
FROM business_edit_requests ber
JOIN businesses b ON b.id = ber.business_id
GROUP BY b.id
ORDER BY pending_count DESC, latest_request DESC
");
// Requests for selected business
$requests = [];
$selectedBiz = null;
if ($bizId) {
$selectedBiz = qone("SELECT * FROM businesses WHERE id=?", [$bizId]);
$whereStatus = $filterStatus === 'all' ? "" : "AND ber.status=?";
$params = $filterStatus === 'all' ? [$bizId] : [$bizId, $filterStatus];
$requests = qall("
SELECT ber.*, u.username
FROM business_edit_requests ber
JOIN users u ON u.id = ber.user_id
WHERE ber.business_id=? $whereStatus
ORDER BY CASE ber.status WHEN 'pending' THEN 0 ELSE 1 END, ber.created_at DESC
", $params);
}
?>
<?php adminTitle('Owner Edit Requests', 'Review and approve or reject listing changes submitted by assigned business owners.') ?>
<div style="display:grid;grid-template-columns:280px 1fr;gap:1.5rem;align-items:start">
<!-- ── Left: business list ─────────────────────────────── -->
<div>
<div style="font-family:'Oswald',sans-serif;font-size:.72rem;letter-spacing:.18em;color:var(--gold);margin-bottom:.65rem">
BUSINESSES WITH EDITS
</div>
<?php if ($bizWithEdits): ?>
<?php foreach ($bizWithEdits as $bwe): ?>
<a href="/admin/biz_edits.php?biz_id=<?=$bwe['id']?>"
style="display:block;padding:.75rem 1rem;border-radius:var(--r);margin-bottom:.35rem;
background:<?=$bizId===$bwe['id']?'var(--gold-dim)':'var(--bg3)'?>;
border:1px solid <?=$bizId===$bwe['id']?'var(--gold-d)':'var(--bdr)'?>;
text-decoration:none;transition:all .18s"
class="<?=$bizId===$bwe['id']?'':'asl-hover'?>">
<div style="font-weight:600;color:var(--text);font-size:.9rem;margin-bottom:.25rem"><?=e($bwe['name'])?></div>
<div class="flex-row" style="gap:.4rem;flex-wrap:wrap">
<?php if ($bwe['pending_count']): ?>
<span class="badge badge-gold"><?=$bwe['pending_count']?> pending</span>
<?php endif; ?>
<?php if ($bwe['approved_count']): ?>
<span class="badge badge-green"><?=$bwe['approved_count']?> approved</span>
<?php endif; ?>
<?php if ($bwe['rejected_count']): ?>
<span class="badge badge-gray"><?=$bwe['rejected_count']?> rejected</span>
<?php endif; ?>
</div>
<div style="font-size:.73rem;color:var(--text-d);margin-top:.25rem">
Last: <?=ago($bwe['latest_request'] ?? '')?>
</div>
</a>
<?php endforeach; ?>
<?php else: ?>
<div style="color:var(--text-d);font-size:.88rem;padding:.75rem">No edit requests yet.</div>
<?php endif; ?>
</div>
<!-- ── Right: requests for selected business ───────────── -->
<div>
<?php if ($selectedBiz): ?>
<!-- Business header -->
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;margin-bottom:1.25rem">
<div>
<div style="font-family:'Oswald',sans-serif;font-size:1.1rem;color:var(--text)"><?=e($selectedBiz['name'])?></div>
<div style="font-size:.82rem;color:var(--text-d)">
<a href="/business.php?slug=<?=e($selectedBiz['slug'])?>" target="_blank" style="color:var(--gold)">View public listing ↗</a>
</div>
</div>
<!-- Status filter -->
<div class="flex-row" style="gap:.4rem">
<?php foreach(['pending'=>'Pending','approved'=>'Approved','rejected'=>'Rejected','all'=>'All'] as $k=>$lbl): ?>
<a href="/admin/biz_edits.php?biz_id=<?=$bizId?>&status=<?=$k?>"
class="btn btn-xs <?=$filterStatus===$k?'btn-primary':'btn-outline'?>"><?=$lbl?></a>
<?php endforeach; ?>
</div>
</div>
<?php
$pendingReqs = array_filter($requests, fn($r) => $r['status'] === 'pending');
?>
<!-- Approve all / Reject all bar -->
<?php if (count($pendingReqs) > 1): ?>
<div style="background:var(--bg3);border:1px solid var(--bdr);border-radius:var(--r);padding:1rem 1.25rem;margin-bottom:1.25rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem">
<span style="font-size:.88rem;color:var(--text-m)">
<strong style="color:var(--gold)"><?=count($pendingReqs)?></strong> pending changes for this listing
</span>
<div class="flex-row" style="gap:.5rem">
<form method="POST">
<?=csrfField()?>
<input type="hidden" name="_act" value="approve_all">
<input type="hidden" name="biz_id" value="<?=$bizId?>">
<button class="btn btn-success btn-sm" data-confirm="Approve ALL <?=count($pendingReqs)?> pending changes for <?=e($selectedBiz['name'])?>?">
✅ Approve All
</button>
</form>
<form method="POST" id="rejectAllForm">
<?=csrfField()?>
<input type="hidden" name="_act" value="reject_all">
<input type="hidden" name="biz_id" value="<?=$bizId?>">
<input type="text" name="admin_notes" class="finput" style="width:200px;font-size:.8rem;padding:.35rem .6rem" placeholder="Reason (optional)">
<button class="btn btn-danger btn-sm" data-confirm="Reject ALL pending changes?">
✕ Reject All
</button>
</form>
</div>
</div>
<?php endif; ?>
<!-- Individual requests -->
<?php if ($requests): ?>
<?php foreach ($requests as $req): ?>
<?php
$isPending = $req['status'] === 'pending';
$label = $fieldLabels[$req['field']] ?? ucfirst($req['field']);
$isHours = $req['field'] === 'hours';
$isStatus = $req['field'] === 'is_active';
?>
<div class="ibox" style="margin-bottom:1.1rem;<?= $isPending ? 'border-color:var(--gold-d)' : '' ?>">
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem;flex-wrap:wrap">
<div>
<span style="font-family:'Oswald',sans-serif;font-size:.82rem;letter-spacing:.1em;color:var(--gold)"><?=e($label)?></span>
<span style="margin-left:.6rem">
<?php if ($isPending): ?>
<span class="badge badge-gold">PENDING</span>
<?php elseif ($req['status']==='approved'): ?>
<span class="badge badge-green">APPROVED</span>
<?php else: ?>
<span class="badge badge-gray">REJECTED</span>
<?php endif; ?>
</span>
<span style="font-size:.75rem;color:var(--text-d);margin-left:.5rem">
by <strong><?=e($req['username'] ?? '')?></strong> · <?=ago($req['created_at'] ?? '')?>
</span>
</div>
</div>
<!-- Old vs New values -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin:1rem 0">
<div>
<div style="font-family:'Oswald',sans-serif;font-size:.65rem;letter-spacing:.16em;color:var(--text-d);margin-bottom:.4rem">CURRENT VALUE</div>
<div style="background:var(--bg2);border:1px solid var(--bdr);border-radius:4px;padding:.65rem .85rem;font-size:.84rem;color:var(--text-m)">
<?php if ($isHours): ?>
<?php $oldH = parseHours($req['old_value']); ?>
<?php if ($oldH): ?>
<?php foreach ($oldH as $d => $t): ?>
<div style="display:flex;gap:.5rem;font-size:.8rem"><span style="color:var(--text-d);min-width:32px"><?=e($d)?></span><span><?=e($t)?></span></div>
<?php endforeach; ?>
<?php else: ?><em style="color:var(--text-d)">Not set</em><?php endif; ?>
<?php elseif ($isStatus): ?>
<?= $req['old_value'] === '1' ? '<span style="color:var(--green-l)">✅ Open / Active</span>' : '<span style="color:var(--text-d)">🔴 Closed / Inactive</span>' ?>
<?php else: ?>
<?= $req['old_value'] !== '' ? e($req['old_value']) : '<em style="color:var(--text-d)">Empty</em>' ?>
<?php endif; ?>
</div>
</div>
<div>
<div style="font-family:'Oswald',sans-serif;font-size:.65rem;letter-spacing:.16em;color:var(--green-l);margin-bottom:.4rem">PROPOSED CHANGE</div>
<div style="background:rgba(42,122,71,.1);border:1px solid rgba(58,165,94,.35);border-radius:4px;padding:.65rem .85rem;font-size:.84rem;color:var(--text)">
<?php if ($isHours): ?>
<?php $newH = parseHours($req['new_value']); ?>
<?php if ($newH): ?>
<?php foreach ($newH as $d => $t): ?>
<div style="display:flex;gap:.5rem;font-size:.8rem"><span style="color:var(--gold);min-width:32px"><?=e($d)?></span><span><?=e($t)?></span></div>
<?php endforeach; ?>
<?php else: ?><em style="color:var(--text-d)">Clear hours</em><?php endif; ?>
<?php elseif ($isStatus): ?>
<?= $req['new_value'] === '1' ? '<span style="color:var(--green-l)">✅ Open / Active</span>' : '<span style="color:var(--text-d)">🔴 Closed / Inactive</span>' ?>
<?php else: ?>
<?= $req['new_value'] !== '' ? e($req['new_value']) : '<em style="color:var(--text-d)">Empty</em>' ?>
<?php endif; ?>
</div>
</div>
</div>
<?php if (!empty($req['admin_notes'])): ?>
<div style="font-size:.82rem;color:var(--text-d);margin-bottom:.75rem">
<strong>Admin note:</strong> <?=e($req['admin_notes'])?>
</div>
<?php endif; ?>
<?php if ($isPending): ?>
<div class="flex-row" style="gap:.6rem;flex-wrap:wrap">
<form method="POST" style="display:inline">
<?=csrfField()?>
<input type="hidden" name="_act" value="approve">
<input type="hidden" name="req_id" value="<?=$req['id']?>">
<input type="hidden" name="biz_id" value="<?=$bizId?>">
<button class="btn btn-success btn-sm">✅ Approve &amp; Apply</button>
</form>
<form method="POST" style="display:flex;gap:.4rem;align-items:center;flex-wrap:wrap">
<?=csrfField()?>
<input type="hidden" name="_act" value="reject">
<input type="hidden" name="req_id" value="<?=$req['id']?>">
<input type="hidden" name="biz_id" value="<?=$bizId?>">
<input type="text" name="admin_notes" class="finput"
style="width:200px;font-size:.8rem;padding:.35rem .6rem"
placeholder="Reason for rejection (optional)">
<button class="btn btn-danger btn-sm">✕ Reject</button>
</form>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="empty" style="padding:2rem">
<div class="empty-ico">📋</div>
<h3>No <?= $filterStatus !== 'all' ? $filterStatus : '' ?> requests for this business</h3>
</div>
<?php endif; ?>
<?php else: ?>
<div class="empty" style="padding:3rem">
<div class="empty-ico">📋</div>
<h3>Select a business from the left to review its edit requests</h3>
<?php if (empty($bizWithEdits)): ?>
<p style="margin-top:.75rem;color:var(--text-d);font-size:.88rem">
No edit requests have been submitted yet. Assigned owners can propose changes from their business listing page.
</p>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div><!-- /grid -->
<?php include __DIR__.'/admin_footer.php'; ?>
+257
View File
@@ -0,0 +1,257 @@
<?php
$adminTitle = 'Businesses';
include __DIR__.'/admin_header.php';
// Handle POST actions
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'add' || $act === 'edit') {
$id = (int)p('id');
$name = ps('name');
if (!$name) { flash('Business name is required.','error'); go('/admin/businesses.php'.($id?"?edit=$id":'')); }
$baseSlug = ps('slug') ?: makeSlug($name);
// Find a unique slug (excluding self on edit)
$slug = $baseSlug; $i = 0;
while (true) {
$candidate = $baseSlug . ($i ? "-$i" : '');
$clash = $act === 'edit'
? qval("SELECT id FROM businesses WHERE slug=? AND id!=?", [$candidate, $id])
: qval("SELECT id FROM businesses WHERE slug=?", [$candidate]);
if (!$clash) { $slug = $candidate; break; }
$i++;
}
// Build hours JSON
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
$hrs = [];
foreach ($days as $d) { $v = ps("hours_$d"); if ($v !== '') $hrs[$d] = $v; }
$fields = [
'name' => $name,
'slug' => $slug,
'category_id' => (int)p('category_id'),
'subcategory' => ps('subcategory'),
'description' => ps('description'),
'address' => ps('address'),
'phone' => ps('phone'),
'email' => ps('email'),
'website' => ps('website'),
'hours' => json_encode($hrs),
'lat' => (float)p('lat', 39.4364),
'lng' => (float)p('lng', -78.9762),
'is_active' => (int)p('is_active', 1),
'is_featured' => (int)p('is_featured', 0),
];
if ($act === 'add') {
qrun("INSERT INTO businesses(name,slug,category_id,subcategory,description,address,phone,email,website,hours,lat,lng,is_active,is_featured)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)", array_values($fields));
flash('Business added!','success');
go('/admin/businesses.php');
} else {
qrun("UPDATE businesses SET name=?,slug=?,category_id=?,subcategory=?,description=?,address=?,phone=?,email=?,website=?,hours=?,lat=?,lng=?,is_active=?,is_featured=? WHERE id=?", array_merge(array_values($fields), [$id]));
flash('Business updated!','success');
go('/admin/businesses.php?edit='.$id);
}
}
if ($act === 'delete') {
$id = (int)p('id');
qrun("DELETE FROM businesses WHERE id=?", [$id]);
flash('Business deleted.','success');
go('/admin/businesses.php');
}
if ($act === 'toggle') {
$id = (int)p('id');
qrun("UPDATE businesses SET is_active = 1 - is_active WHERE id=?", [$id]);
go('/admin/businesses.php');
}
}
$editId = iget('edit');
$editing = $editId ? qone("SELECT * FROM businesses WHERE id=?", [$editId]) : null;
$cats = qall("SELECT * FROM categories ORDER BY sort_order");
$days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
$h = $editing ? parseHours($editing['hours']) : [];
$q = gs('q');
$page = max(1, iget('page'));
$perPage = 25;
$where = $q ? "WHERE b.name LIKE ? OR b.address LIKE ? OR b.subcategory LIKE ?" : "";
$params = $q ? ["%$q%", "%$q%", "%$q%"] : [];
$total = (int)qval("SELECT COUNT(*) FROM businesses b $where", $params);
$pages = max(1, (int)ceil($total / $perPage));
$page = min($page, $pages);
$offset = ($page - 1) * $perPage;
$bizs = qall("SELECT b.*,c.name cat FROM businesses b LEFT JOIN categories c ON c.id=b.category_id $where ORDER BY b.name LIMIT $perPage OFFSET $offset", $params);
?>
<?php adminTitle($editing ? 'Edit Business: '.e($editing['name']) : 'Manage Businesses') ?>
<!-- Add/Edit Form -->
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title"><?= $editing ? '&#x270F;&#xFE0F; EDIT: '.strtoupper(e($editing['name'])) : '&#x2795; ADD NEW BUSINESS' ?></div>
<form method="POST" action="/admin/businesses.php<?= $editing ? '?edit='.$editing['id'] : '' ?>">
<?=csrfField()?>
<input type="hidden" name="_act" value="<?= $editing ? 'edit' : 'add' ?>">
<?php if ($editing): ?><input type="hidden" name="id" value="<?= $editing['id'] ?>"><?php endif; ?>
<div class="form-row">
<div class="fg">
<label class="flabel">BUSINESS NAME *</label>
<input type="text" name="name" class="finput" value="<?=e($editing['name']??'')?>" required>
</div>
<div class="fg">
<label class="flabel">SLUG (auto-generated if blank)</label>
<input type="text" name="slug" class="finput" value="<?=e($editing['slug']??'')?>" placeholder="auto-generated from name">
</div>
</div>
<div class="form-row">
<div class="fg">
<label class="flabel">CATEGORY</label>
<select name="category_id" class="fselect">
<?php foreach ($cats as $c): ?>
<option value="<?=$c['id']?>"<?= (int)($editing['category_id']??0) === (int)$c['id'] ? ' selected' : '' ?>><?=e($c['icon'].' '.$c['name'])?></option>
<?php endforeach; ?>
</select>
</div>
<div class="fg">
<label class="flabel">SUBCATEGORY</label>
<input type="text" name="subcategory" class="finput" value="<?=e($editing['subcategory']??'')?>" placeholder="e.g. Italian Restaurant">
</div>
</div>
<div class="fg">
<label class="flabel">DESCRIPTION</label>
<textarea name="description" class="ftextarea" style="min-height:90px"><?=e($editing['description']??'')?></textarea>
</div>
<div class="form-row">
<div class="fg">
<label class="flabel">ADDRESS</label>
<input type="text" name="address" class="finput" value="<?=e($editing['address']??'')?>">
</div>
<div class="fg">
<label class="flabel">PHONE</label>
<input type="text" name="phone" class="finput" value="<?=e($editing['phone']??'')?>">
</div>
</div>
<div class="form-row">
<div class="fg">
<label class="flabel">EMAIL</label>
<input type="email" name="email" class="finput" value="<?=e($editing['email']??'')?>">
</div>
<div class="fg">
<label class="flabel">WEBSITE (no https://)</label>
<input type="text" name="website" class="finput" value="<?=e($editing['website']??'')?>" placeholder="example.com">
</div>
</div>
<div class="form-row">
<div class="fg">
<label class="flabel">LATITUDE</label>
<input type="number" name="lat" class="finput" value="<?=e($editing['lat']??39.4364)?>" step="0.0001">
</div>
<div class="fg">
<label class="flabel">LONGITUDE</label>
<input type="number" name="lng" class="finput" value="<?=e($editing['lng']??-78.9762)?>" step="0.0001">
</div>
</div>
<div style="margin-bottom:1rem">
<label class="flabel" style="margin-bottom:.6rem">HOURS OF OPERATION</label>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:.5rem">
<?php foreach ($days as $d): ?>
<div style="display:flex;gap:.4rem;align-items:center">
<span style="font-family:'Oswald',sans-serif;font-size:.72rem;color:var(--gold);width:28px;flex-shrink:0"><?=$d?></span>
<input type="text" name="hours_<?=$d?>" class="finput" style="font-size:.8rem;padding:.4rem .6rem"
value="<?=e($h[$d]??'')?>" placeholder="e.g. 9am-5pm">
</div>
<?php endforeach; ?>
</div>
</div>
<div class="form-row" style="margin-bottom:.75rem">
<div class="fg">
<label class="flabel">STATUS</label>
<select name="is_active" class="fselect">
<option value="1"<?= ($editing['is_active'] ?? 1) == 1 ? ' selected' : '' ?>>Active</option>
<option value="0"<?= ($editing['is_active'] ?? 1) == 0 ? ' selected' : '' ?>>Inactive (hidden)</option>
</select>
</div>
<div class="fg">
<label class="flabel">FEATURED</label>
<select name="is_featured" class="fselect">
<option value="0"<?= ($editing['is_featured'] ?? 0) == 0 ? ' selected' : '' ?>>Not Featured</option>
<option value="1"<?= ($editing['is_featured'] ?? 0) == 1 ? ' selected' : '' ?>>Featured</option>
</select>
</div>
</div>
<div class="flex-row">
<button type="submit" class="btn btn-primary"><?= $editing ? 'Update Business' : 'Add Business' ?></button>
<?php if ($editing): ?>
<a href="/admin/businesses.php" class="btn btn-secondary">Cancel / Add New</a>
<?php endif; ?>
</div>
</form>
</div>
<!-- Businesses list -->
<div class="admin-sec-title">ALL BUSINESSES (<?=$total?>)</div>
<form method="GET" action="/admin/businesses.php" style="display:flex;gap:.5rem;margin-bottom:1rem">
<input type="text" name="q" class="finput" style="max-width:300px" value="<?=e($q)?>" placeholder="Search businesses...">
<button type="submit" class="btn btn-secondary btn-sm">Search</button>
<?php if ($q): ?><a href="/admin/businesses.php" class="btn btn-outline btn-sm">Clear</a><?php endif; ?>
</form>
<div class="tbl-wrap">
<table class="dtbl">
<thead>
<tr><th>NAME</th><th>CATEGORY</th><th>PHONE</th><th>STATUS</th><th>FEATURED</th><th>ACTIONS</th></tr>
</thead>
<tbody>
<?php foreach ($bizs as $b): ?>
<tr<?= ($editing && $editing['id'] == $b['id']) ? ' style="background:rgba(201,168,76,.06)"' : '' ?>>
<td><a href="/business.php?slug=<?=e($b['slug'])?>" class="td-name" target="_blank"><?=e($b['name'])?></a></td>
<td><span style="font-size:.78rem;color:var(--text-d)"><?=e($b['cat']??'--')?></span></td>
<td style="font-size:.8rem"><?=e($b['phone'])?></td>
<td><?= $b['is_active'] ? '<span class="badge badge-green">Active</span>' : '<span class="badge badge-gray">Inactive</span>' ?></td>
<td><?= $b['is_featured'] ? '<span class="badge badge-gold">Featured</span>' : '--' ?></td>
<td>
<div class="flex-row" style="gap:.3rem">
<a href="/admin/businesses.php?edit=<?=$b['id']?>" class="btn btn-xs <?= ($editing && $editing['id']==$b['id']) ? 'btn-primary' : 'btn-outline' ?>">Edit</a>
<form method="POST" action="/admin/businesses.php" style="display:inline">
<?=csrfField()?>
<input type="hidden" name="_act" value="toggle">
<input type="hidden" name="id" value="<?=$b['id']?>">
<button class="btn btn-xs btn-secondary"><?= $b['is_active'] ? 'Deactivate' : 'Activate' ?></button>
</form>
<form method="POST" action="/admin/businesses.php" style="display:inline">
<?=csrfField()?>
<input type="hidden" name="_act" value="delete">
<input type="hidden" name="id" value="<?=$b['id']?>">
<button class="btn btn-xs btn-danger" data-confirm="Permanently delete '<?=e($b['name'])?>' and all its data?">Delete</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($pages > 1): ?>
<div style="display:flex;gap:.5rem;margin-top:1rem;flex-wrap:wrap">
<?php for ($i = 1; $i <= $pages; $i++): ?>
<a href="?<?=http_build_query(array_filter(['q'=>$q,'page'=>$i]))?>" class="btn btn-sm<?= $i===$page ? ' btn-primary' : ' btn-outline'?>"><?=$i?></a>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php include __DIR__.'/admin_footer.php'; ?>
+69
View File
@@ -0,0 +1,69 @@
<?php
$adminTitle = 'Categories';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'add') {
$name = ps('name'); $icon = ps('icon') ?: '🏢'; $sort = (int)p('sort_order',0);
if ($name) { qrun("INSERT OR IGNORE INTO categories(name,icon,sort_order)VALUES(?,?,?)",[$name,$icon,$sort]); flash('Category added!','success'); }
}
if ($act === 'edit') {
qrun("UPDATE categories SET name=?,icon=?,sort_order=? WHERE id=?",[ps('name'),ps('icon'),(int)p('sort_order'),(int)p('id')]);
flash('Category updated!','success');
}
if ($act === 'delete') {
$id = (int)p('id');
$cnt = (int)qval("SELECT COUNT(*) FROM businesses WHERE category_id=?",[$id]);
if ($cnt > 0) { flash("Cannot delete — $cnt businesses use this category.",'error'); }
else { qrun("DELETE FROM categories WHERE id=?",[$id]); flash('Category deleted.','success'); }
}
go('/admin/categories.php');
}
$editId = iget('edit');
$editing = $editId ? qone("SELECT * FROM categories WHERE id=?",[$editId]) : null;
$cats = qall("SELECT c.*,(SELECT COUNT(*) FROM businesses WHERE category_id=c.id) biz_count FROM categories c ORDER BY c.sort_order,c.name");
?>
<?php adminTitle('Manage Business Categories') ?>
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem;max-width:600px">
<div class="ibox-title"><?=$editing?'✏️ EDIT CATEGORY':' ADD CATEGORY'?></div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="<?=$editing?'edit':'add'?>">
<?php if($editing): ?><input type="hidden" name="id" value="<?=$editing['id']?>"><?php endif; ?>
<div class="form-row">
<div class="fg"><label class="flabel">NAME *</label><input type="text" name="name" class="finput" value="<?=e($editing['name']??'')?>" required placeholder="e.g. Dining & Food"></div>
<div class="fg"><label class="flabel">EMOJI ICON</label><input type="text" name="icon" class="finput" value="<?=e($editing['icon']??'🏢')?>" placeholder="🏢" style="font-size:1.2rem"></div>
</div>
<div class="fg"><label class="flabel">SORT ORDER</label><input type="number" name="sort_order" class="finput" value="<?=$editing['sort_order']??0?>" style="max-width:120px"></div>
<div class="flex-row">
<button type="submit" class="btn btn-primary btn-sm"><?=$editing?'Update':'Add Category'?></button>
<?php if($editing): ?><a href="/admin/categories.php" class="btn btn-secondary btn-sm">Cancel</a><?php endif; ?>
</div>
</form>
</div>
<div class="admin-sec-title">ALL CATEGORIES (<?=count($cats)?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>ICON</th><th>NAME</th><th>BUSINESSES</th><th>SORT</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($cats as $c): ?>
<tr>
<td style="font-size:1.4rem;text-align:center"><?=$c['icon']?></td>
<td class="td-name"><?=e($c['name'])?></td>
<td><span class="badge badge-blue"><?=$c['biz_count']?></span></td>
<td><?=$c['sort_order']?></td>
<td><div class="flex-row" style="gap:.3rem">
<a href="/admin/categories.php?edit=<?=$c['id']?>" class="btn btn-xs btn-outline">✏️ Edit</a>
<?php if(!$c['biz_count']): ?>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$c['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete category?">🗑️</button></form>
<?php else: ?><span style="font-size:.75rem;color:var(--text-d)">In use</span><?php endif; ?>
</div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include __DIR__.'/admin_footer.php'; ?>
+111
View File
@@ -0,0 +1,111 @@
<?php
$adminTitle = 'Events';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'approve') { qrun("UPDATE events SET status='approved' WHERE id=?",[(int)p('id')]); flash('Event approved!','success'); go('/admin/events.php'); }
if ($act === 'reject') { qrun("UPDATE events SET status='rejected' WHERE id=?",[(int)p('id')]); flash('Event rejected.','success'); go('/admin/events.php'); }
if ($act === 'delete') { qrun("DELETE FROM events WHERE id=?",[(int)p('id')]); flash('Event deleted.','success'); go('/admin/events.php'); }
if ($act === 'feature') { qrun("UPDATE events SET is_featured=1-is_featured WHERE id=?",[(int)p('id')]); go('/admin/events.php'); }
if ($act === 'add' || $act === 'edit') {
$f=['title'=>ps('title'),'description'=>ps('description'),'venue'=>ps('venue'),'address'=>ps('address'),
'event_date'=>ps('event_date'),'event_time'=>ps('event_time'),'end_date'=>ps('end_date')?:null,
'end_time'=>ps('end_time'),'cost'=>ps('cost'),'website'=>ps('website'),'contact_name'=>ps('contact_name'),
'contact_email'=>ps('contact_email'),'contact_phone'=>ps('contact_phone'),
'status'=>p('status','approved'),'is_featured'=>(int)p('is_featured',0)];
if (!$f['title'] || !$f['event_date']) { flash('Title and date required.','error'); go('/admin/events.php'); }
if ($act === 'add') {
qrun("INSERT INTO events(title,description,venue,address,event_date,event_time,end_date,end_time,cost,website,contact_name,contact_email,contact_phone,status,is_featured,submitted_by)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",array_merge(array_values($f),[uid()]));
flash('Event added!','success');
} else {
$id = (int)p('id');
qrun("UPDATE events SET title=?,description=?,venue=?,address=?,event_date=?,event_time=?,end_date=?,end_time=?,cost=?,website=?,contact_name=?,contact_email=?,contact_phone=?,status=?,is_featured=? WHERE id=?",array_merge(array_values($f),[$id]));
flash('Event updated!','success');
}
go('/admin/events.php');
}
}
$filter = gs('filter') ?: 'all';
$editId = iget('edit');
$editing = $editId ? qone("SELECT * FROM events WHERE id=?",[$editId]) : null;
$where = match($filter) { 'pending'=>"WHERE status='pending'",'approved'=>"WHERE status='approved'", default=>"" };
$events = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.submitted_by $where ORDER BY CASE status WHEN 'pending' THEN 0 ELSE 1 END,event_date DESC");
?>
<?php adminTitle('Manage Events') ?>
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title"><?=$editing ? '✏️ EDIT EVENT: '.strtoupper(e($editing['title'])) : ' ADD EVENT'?></div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="<?=$editing?'edit':'add'?>">
<?php if($editing): ?><input type="hidden" name="id" value="<?=$editing['id']?>"><?php endif; ?>
<div class="fg"><label class="flabel">TITLE *</label><input type="text" name="title" class="finput" value="<?=e($editing['title']??'')?>" required></div>
<div class="fg"><label class="flabel">DESCRIPTION</label><textarea name="description" class="ftextarea" style="min-height:80px"><?=e($editing['description']??'')?></textarea></div>
<div class="form-row">
<div class="fg"><label class="flabel">VENUE</label><input type="text" name="venue" class="finput" value="<?=e($editing['venue']??'')?>"></div>
<div class="fg"><label class="flabel">ADDRESS</label><input type="text" name="address" class="finput" value="<?=e($editing['address']??'')?>"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">START DATE *</label><input type="date" name="event_date" class="finput" value="<?=e($editing['event_date']??'')?>" required></div>
<div class="fg"><label class="flabel">START TIME</label><input type="text" name="event_time" class="finput" value="<?=e($editing['event_time']??'')?>" placeholder="e.g. 7:00 PM"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">END DATE</label><input type="date" name="end_date" class="finput" value="<?=e($editing['end_date']??'')?>"></div>
<div class="fg"><label class="flabel">END TIME</label><input type="text" name="end_time" class="finput" value="<?=e($editing['end_time']??'')?>"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">COST</label><input type="text" name="cost" class="finput" value="<?=e($editing['cost']??'Free')?>"></div>
<div class="fg"><label class="flabel">WEBSITE</label><input type="text" name="website" class="finput" value="<?=e($editing['website']??'')?>"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">CONTACT NAME</label><input type="text" name="contact_name" class="finput" value="<?=e($editing['contact_name']??'')?>"></div>
<div class="fg"><label class="flabel">CONTACT PHONE</label><input type="text" name="contact_phone" class="finput" value="<?=e($editing['contact_phone']??'')?>"></div>
</div>
<div class="fg"><label class="flabel">CONTACT EMAIL</label><input type="email" name="contact_email" class="finput" value="<?=e($editing['contact_email']??'')?>"></div>
<div class="form-row">
<div class="fg"><label class="flabel">STATUS</label><select name="status" class="fselect"><option value="approved"<?=($editing['status']??'approved')==='approved'?' selected':''?>>Approved (public)</option><option value="pending"<?=($editing['status']??'')==='pending'?' selected':''?>>Pending review</option><option value="rejected"<?=($editing['status']??'')==='rejected'?' selected':''?>>Rejected</option></select></div>
<div class="fg"><label class="flabel">FEATURED</label><select name="is_featured" class="fselect"><option value="0"<?=!($editing['is_featured']??0)?' selected':''?>>Normal</option><option value="1"<?=($editing['is_featured']??0)?' selected':''?>>★ Featured</option></select></div>
</div>
<div class="flex-row">
<button type="submit" class="btn btn-primary"><?=$editing?'Update Event':'Add Event'?></button>
<?php if($editing): ?><a href="/admin/events.php" class="btn btn-secondary">Cancel</a><?php endif; ?>
</div>
</form>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem;margin-bottom:.75rem">
<div class="admin-sec-title" style="margin-bottom:0">ALL EVENTS (<?=count($events)?>)</div>
<div class="flex-row" style="gap:.4rem">
<?php foreach(['all'=>'All','pending'=>'Pending','approved'=>'Approved'] as $k=>$lbl): ?>
<a href="/admin/events.php?filter=<?=$k?>" class="btn btn-xs<?=$filter===$k?' btn-primary':' btn-outline'?>"><?=$lbl?></a>
<?php endforeach; ?>
</div>
</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>TITLE</th><th>DATE</th><th>SUBMITTED BY</th><th>STATUS</th><th>FEATURED</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($events as $ev): ?>
<tr>
<td class="td-name" style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?=e($ev['title'])?></td>
<td style="font-size:.8rem"><?=fdate($ev['event_date'])?></td>
<td style="font-size:.8rem"><?=e($ev['username']??'Admin')?></td>
<td><?php $sc=['approved'=>'badge-green','pending'=>'badge-gold','rejected'=>'badge-red']; ?><span class="badge <?=$sc[$ev['status']]??"badge-gray"?>"><?=strtoupper($ev['status'])?></span></td>
<td><?=$ev['is_featured']?'<span class="badge badge-gold">★</span>':'—'?></td>
<td>
<div class="flex-row" style="gap:.3rem">
<a href="/admin/events.php?edit=<?=$ev['id']?>" class="btn btn-xs btn-outline">✏️</a>
<?php if($ev['status']==='pending'): ?><form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="approve"><input type="hidden" name="id" value="<?=$ev['id']?>"><button class="btn btn-xs btn-success">Approve</button></form><form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="reject"><input type="hidden" name="id" value="<?=$ev['id']?>"><button class="btn btn-xs btn-secondary">Reject</button></form><?php endif; ?>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="feature"><input type="hidden" name="id" value="<?=$ev['id']?>"><button class="btn btn-xs btn-secondary"><?=$ev['is_featured']?'Unfeature':'Feature'?></button></form>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$ev['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete event?">🗑️</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php include __DIR__.'/admin_footer.php'; ?>
+159
View File
@@ -0,0 +1,159 @@
<?php
$adminTitle = 'Dashboard';
include __DIR__.'/admin_header.php';
$stats = [
'Businesses' => qval("SELECT COUNT(*) FROM businesses WHERE is_active=1"),
'Users' => qval("SELECT COUNT(*) FROM users"),
'Reviews' => qval("SELECT COUNT(*) FROM reviews"),
'Events' => qval("SELECT COUNT(*) FROM events WHERE status='approved'"),
'Pending Events' => qval("SELECT COUNT(*) FROM events WHERE status='pending'"),
'Pending Edits' => qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'"),
'Open Reports' => qval("SELECT COUNT(*) FROM reports WHERE status='open'"),
'Active Alerts' => qval("SELECT COUNT(*) FROM alerts WHERE is_active=1"),
];
$recentBiz = qall("SELECT * FROM businesses ORDER BY created_at DESC LIMIT 5");
$recentUsers = qall("SELECT * FROM users ORDER BY created_at DESC LIMIT 5");
$pendingEvts = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.submitted_by WHERE e.status='pending' ORDER BY e.created_at DESC LIMIT 5");
$openReports = qall("SELECT r.*,b.name biz,u.username reporter FROM reports r JOIN businesses b ON b.id=r.business_id JOIN users u ON u.id=r.user_id WHERE r.status='open' ORDER BY r.created_at DESC LIMIT 5");
$pendingEdits = qall("
SELECT ber.*, b.name biz_name, b.slug biz_slug, b.id biz_id, u.username
FROM business_edit_requests ber
JOIN businesses b ON b.id = ber.business_id
JOIN users u ON u.id = ber.user_id
WHERE ber.status = 'pending'
ORDER BY ber.created_at DESC
LIMIT 10
");
?>
<?php adminTitle('Site Overview','Welcome to the Discover Keyser WV administration panel.') ?>
<div class="stat-cards">
<?php foreach($stats as $lbl=>$val): ?>
<div class="stat-card">
<div class="sc-n" style="<?=$lbl==='Pending Edits'&&$val>0?'color:var(--gold)':($lbl==='Open Reports'&&$val>0?'color:var(--red-l)':'')?>">
<?=$val?>
</div>
<div class="sc-l"><?=e($lbl)?></div>
</div>
<?php endforeach; ?>
</div>
<!-- Pending owner edits — top priority -->
<?php if ($pendingEdits): ?>
<div style="margin-bottom:2rem">
<div class="admin-sec-title" style="color:var(--gold)">
✏️ PENDING OWNER EDIT REQUESTS (<?=count($pendingEdits)?>)
</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>BUSINESS</th><th>FIELD</th><th>SUBMITTED BY</th><th>PROPOSED VALUE</th><th>SUBMITTED</th><th></th></tr></thead>
<tbody>
<?php
$fieldLabels = ['description'=>'Description','address'=>'Address','phone'=>'Phone',
'email'=>'Email','hours'=>'Hours','is_active'=>'Status'];
foreach ($pendingEdits as $pe):
$preview = $pe['field'] === 'hours'
? '(hours update)'
: ($pe['field'] === 'is_active'
? ($pe['new_value']==='1' ? 'Open/Active' : 'Closed/Inactive')
: substr($pe['new_value'], 0, 60).(strlen($pe['new_value'])>60?'…':''));
?>
<tr>
<td><a href="/business.php?slug=<?=e($pe['biz_slug'])?>" target="_blank" class="td-name"><?=e($pe['biz_name'])?></a></td>
<td><span class="badge badge-gold"><?=e($fieldLabels[$pe['field']] ?? $pe['field'])?></span></td>
<td style="font-size:.82rem"><?=e($pe['username'] ?? '')?></td>
<td style="font-size:.82rem;max-width:220px;color:var(--text-m)"><?=e($preview)?></td>
<td style="font-size:.78rem;color:var(--text-d)"><?=ago($pe['created_at'] ?? '')?></td>
<td><a href="/admin/biz_edits.php?biz_id=<?=$pe['biz_id']?>" class="btn btn-xs btn-primary">Review</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<a href="/admin/biz_edits.php" class="btn btn-outline btn-sm" style="margin-top:.75rem">View All Edit Requests</a>
</div>
<?php endif; ?>
<div class="g2" style="gap:1.5rem;margin-bottom:2rem">
<?php if($pendingEvts): ?>
<div>
<div class="admin-sec-title">⏳ PENDING EVENTS (<?=count($pendingEvts)?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>EVENT</th><th>SUBMITTED BY</th><th>DATE</th><th></th></tr></thead>
<tbody>
<?php foreach($pendingEvts as $ev): ?>
<tr>
<td class="td-name"><?=e($ev['title'])?></td>
<td><?=e($ev['username']??'—')?></td>
<td><?=fdate($ev['event_date']??'')?></td>
<td><a href="/admin/events.php?edit=<?=$ev['id']?>" class="btn btn-xs btn-primary">Review</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php if($openReports): ?>
<div>
<div class="admin-sec-title">🚩 OPEN REPORTS (<?=count($openReports)?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>BUSINESS</th><th>TYPE</th><th>FROM</th><th></th></tr></thead>
<tbody>
<?php foreach($openReports as $r): ?>
<tr>
<td class="td-name"><?=e($r['biz'])?></td>
<td><span class="badge badge-gold"><?=e($r['type'])?></span></td>
<td><?=e($r['reporter'])?></td>
<td><a href="/admin/reports.php" class="btn btn-xs btn-outline">View</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
<div class="g2">
<div>
<div class="admin-sec-title">🏢 RECENT BUSINESSES</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>NAME</th><th>STATUS</th><th></th></tr></thead>
<tbody>
<?php foreach($recentBiz as $b): ?>
<tr>
<td class="td-name"><?=e($b['name'])?></td>
<td><?=$b['is_active']?'<span class="badge badge-green">Active</span>':'<span class="badge badge-gray">Inactive</span>'?></td>
<td><a href="/admin/businesses.php?edit=<?=$b['id']?>" class="btn btn-xs btn-outline">Edit</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<a href="/admin/businesses.php" class="btn btn-outline btn-sm" style="margin-top:.75rem">Manage All Businesses</a>
</div>
<div>
<div class="admin-sec-title">👥 RECENT USERS</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>USERNAME</th><th>JOINED</th><th></th></tr></thead>
<tbody>
<?php foreach($recentUsers as $u): ?>
<tr>
<td class="td-name"><?=e($u['username'])?><?=$u['is_admin']?' <span class="badge badge-gold">ADMIN</span>':''?></td>
<td><?=fdate($u['created_at']??'')?></td>
<td><a href="/admin/users.php?edit=<?=$u['id']?>" class="btn btn-xs btn-outline">Edit</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<a href="/admin/users.php" class="btn btn-outline btn-sm" style="margin-top:.75rem">Manage All Users</a>
</div>
</div>
<?php include __DIR__.'/admin_footer.php'; ?>
+179
View File
@@ -0,0 +1,179 @@
<?php
$adminTitle = 'Menus';
include __DIR__.'/admin_header.php';
$bizId = iget('biz_id');
$biz = $bizId ? qone("SELECT * FROM businesses WHERE id=?",[$bizId]) : null;
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'add_section') {
$bid = (int)p('business_id'); $name = ps('sec_name');
if ($bid && $name) {
$ord = (int)qval("SELECT COALESCE(MAX(sort_order),0)+1 FROM menu_sections WHERE business_id=?",[$bid]);
qrun("INSERT INTO menu_sections(business_id,name,description,sort_order)VALUES(?,?,?,?)",[$bid,$name,ps('sec_desc'),$ord]);
flash('Section added!','success');
}
go("/admin/menus.php?biz_id=$bid");
}
if ($act === 'edit_section') {
$sid = (int)p('sid');
qrun("UPDATE menu_sections SET name=?,description=?,sort_order=? WHERE id=?",[ps('sec_name'),ps('sec_desc'),(int)p('sec_order'),$sid]);
flash('Section updated!','success'); go("/admin/menus.php?biz_id=$bizId");
}
if ($act === 'del_section') {
qrun("DELETE FROM menu_sections WHERE id=?",[(int)p('sid')]);
flash('Section deleted.','success'); go("/admin/menus.php?biz_id=$bizId");
}
if ($act === 'add_item') {
$sid = (int)p('section_id');
$name = ps('item_name');
if ($sid && $name) {
$ord = (int)qval("SELECT COALESCE(MAX(sort_order),0)+1 FROM menu_items WHERE section_id=?",[$sid]);
qrun("INSERT INTO menu_items(section_id,name,description,price,is_available,sort_order)VALUES(?,?,?,?,?,?)",[$sid,$name,ps('item_desc'),(float)p('item_price',0),(int)p('is_available',1),$ord]);
flash('Item added!','success');
}
go("/admin/menus.php?biz_id=$bizId");
}
if ($act === 'edit_item') {
$iid = (int)p('iid');
qrun("UPDATE menu_items SET name=?,description=?,price=?,is_available=?,sort_order=? WHERE id=?",[ps('item_name'),ps('item_desc'),(float)p('item_price',0),(int)p('is_available',1),(int)p('item_order'),$iid]);
flash('Item updated!','success'); go("/admin/menus.php?biz_id=$bizId");
}
if ($act === 'del_item') {
qrun("DELETE FROM menu_items WHERE id=?",[(int)p('iid')]);
flash('Item removed.','success'); go("/admin/menus.php?biz_id=$bizId");
}
}
$bizList = qall("SELECT id,name FROM businesses WHERE is_active=1 ORDER BY name");
if ($biz) {
$sections = qall("SELECT * FROM menu_sections WHERE business_id=? ORDER BY sort_order",[$bizId]);
$items = [];
foreach ($sections as $s) $items[$s['id']] = qall("SELECT * FROM menu_items WHERE section_id=? ORDER BY sort_order",[$s['id']]);
}
?>
<?php adminTitle('Manage Menus','Edit menus, sections, and items for any business.') ?>
<!-- Business selector -->
<div class="ibox" style="max-width:500px;margin-bottom:2rem">
<div class="ibox-title">SELECT BUSINESS TO MANAGE MENU</div>
<form method="GET" style="display:flex;gap:.65rem">
<select name="biz_id" class="fselect" style="flex:1">
<option value="">— Choose a business —</option>
<?php foreach($bizList as $b): ?><option value="<?=$b['id']?>"<?=$bizId===$b['id']?' selected':''?>><?=e($b['name'])?></option><?php endforeach; ?>
</select>
<button type="submit" class="btn btn-primary btn-sm">Load Menu</button>
</form>
</div>
<?php if($biz): ?>
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem">
<h2 style="font-size:1.3rem;color:var(--gold);font-family:'Oswald',sans-serif;letter-spacing:.1em"><?=e($biz['name'])?> — Menu</h2>
<a href="/business.php?slug=<?=e($biz['slug'])?>" target="_blank" class="btn btn-outline btn-xs">View Listing ↗</a>
</div>
<!-- Add section -->
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:1.5rem">
<div class="ibox-title"> ADD MENU SECTION</div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="add_section"><input type="hidden" name="business_id" value="<?=$bizId?>">
<div class="form-row">
<div class="fg"><label class="flabel">SECTION NAME *</label><input type="text" name="sec_name" class="finput" required placeholder="e.g. Appetizers, Pasta, Desserts"></div>
<div class="fg"><label class="flabel">DESCRIPTION</label><input type="text" name="sec_desc" class="finput" placeholder="Optional description"></div>
</div>
<button type="submit" class="btn btn-primary btn-sm">Add Section</button>
</form>
</div>
<?php foreach($sections as $sec): ?>
<div class="ibox" style="margin-bottom:1.5rem">
<!-- Section header -->
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.9rem;padding-bottom:.6rem;border-bottom:1px solid var(--bdr)">
<div>
<span style="font-family:'Oswald',sans-serif;color:var(--gold);letter-spacing:.1em"><?=e($sec['name'])?></span>
<?php if($sec['description']): ?><span style="font-size:.78rem;color:var(--text-d);margin-left:.5rem"><?=e($sec['description'])?></span><?php endif; ?>
</div>
<div class="flex-row" style="gap:.3rem">
<button onclick="document.getElementById('esf<?=$sec['id']?>').classList.toggle('hidden')" class="btn btn-xs btn-secondary">✏️ Edit Section</button>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="del_section"><input type="hidden" name="sid" value="<?=$sec['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete section and ALL its items?">🗑️ Delete</button></form>
</div>
</div>
<!-- Edit section -->
<div id="esf<?=$sec['id']?>" class="hidden" style="background:var(--bg4);border:1px solid var(--bdr);border-radius:4px;padding:.85rem;margin-bottom:.85rem">
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="edit_section"><input type="hidden" name="sid" value="<?=$sec['id']?>">
<div class="form-row">
<div class="fg"><label class="flabel">NAME</label><input type="text" name="sec_name" class="finput" value="<?=e($sec['name'])?>" required></div>
<div class="fg"><label class="flabel">DESCRIPTION</label><input type="text" name="sec_desc" class="finput" value="<?=e($sec['description'])?>"></div>
</div>
<div class="fg"><label class="flabel">SORT ORDER</label><input type="number" name="sec_order" class="finput" style="max-width:100px" value="<?=$sec['sort_order']?>"></div>
<button type="submit" class="btn btn-success btn-sm">Save Section</button>
</form>
</div>
<!-- Items -->
<?php if($items[$sec['id']]): ?>
<div class="tbl-wrap" style="margin-bottom:.85rem">
<table class="dtbl">
<thead><tr><th>ITEM NAME</th><th>DESCRIPTION</th><th>PRICE</th><th>AVAIL.</th><th>ORDER</th><th></th></tr></thead>
<tbody>
<?php foreach($items[$sec['id']] as $item): ?>
<tr>
<td class="td-name"><?=e($item['name'])?></td>
<td style="font-size:.8rem;max-width:180px"><?=e(substr($item['description'],0,80))?></td>
<td style="color:var(--gold);font-family:'Oswald',sans-serif"><?=$item['price']>0?money($item['price']):'—'?></td>
<td><?=$item['is_available']?'<span class="badge badge-green">Yes</span>':'<span class="badge badge-gray">No</span>'?></td>
<td><?=$item['sort_order']?></td>
<td>
<div class="flex-row" style="gap:.3rem">
<button onclick="document.getElementById('eif<?=$item['id']?>').classList.toggle('hidden')" class="btn btn-xs btn-outline">✏️</button>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="del_item"><input type="hidden" name="iid" value="<?=$item['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete item?">🗑️</button></form>
</div>
<div id="eif<?=$item['id']?>" class="hidden" style="background:var(--bg2);border:1px solid var(--bdr);border-radius:4px;padding:.75rem;margin-top:.4rem;min-width:320px">
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="edit_item"><input type="hidden" name="iid" value="<?=$item['id']?>">
<div class="form-row">
<div class="fg"><label class="flabel">NAME *</label><input type="text" name="item_name" class="finput" value="<?=e($item['name'])?>" required></div>
<div class="fg"><label class="flabel">PRICE</label><input type="number" name="item_price" class="finput" value="<?=$item['price']?>" step="0.01" min="0"></div>
</div>
<div class="fg"><label class="flabel">DESCRIPTION</label><input type="text" name="item_desc" class="finput" value="<?=e($item['description'])?>"></div>
<div class="form-row">
<div class="fg"><label class="flabel">AVAILABLE</label><select name="is_available" class="fselect"><option value="1"<?=$item['is_available']?' selected':''?>>Yes</option><option value="0"<?=!$item['is_available']?' selected':''?>>No</option></select></div>
<div class="fg"><label class="flabel">SORT ORDER</label><input type="number" name="item_order" class="finput" value="<?=$item['sort_order']?>"></div>
</div>
<button type="submit" class="btn btn-success btn-sm">Save Item</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<!-- Add item -->
<details><summary style="cursor:pointer;font-size:.82rem;color:var(--gold);font-family:'Oswald',sans-serif;letter-spacing:.08em"> Add Item to "<?=e($sec['name'])?>"</summary>
<div style="padding:.75rem 0">
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="add_item"><input type="hidden" name="section_id" value="<?=$sec['id']?>">
<div class="form-row">
<div class="fg"><label class="flabel">ITEM NAME *</label><input type="text" name="item_name" class="finput" required placeholder="e.g. Spaghetti Carbonara"></div>
<div class="fg"><label class="flabel">PRICE ($)</label><input type="number" name="item_price" class="finput" value="0" step="0.01" min="0"></div>
</div>
<div class="fg"><label class="flabel">DESCRIPTION</label><input type="text" name="item_desc" class="finput" placeholder="Brief description…"></div>
<div class="fg"><label class="flabel">AVAILABLE</label><select name="is_available" class="fselect"><option value="1">Yes</option><option value="0">No</option></select></div>
<button type="submit" class="btn btn-primary btn-sm">Add Item</button>
</form>
</div>
</details>
</div>
<?php endforeach; ?>
<?php if(empty($sections)): ?>
<div class="empty" style="padding:2rem"><div class="empty-ico">🍽️</div><h3>No menu sections yet</h3><p style="margin-top:.5rem">Use the form above to add the first section.</p></div>
<?php endif; ?>
<?php endif; ?>
<style>.hidden{display:none!important}</style>
<?php include __DIR__.'/admin_footer.php'; ?>
+75
View File
@@ -0,0 +1,75 @@
<?php
$adminTitle = 'Assign Business Owners';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'assign') {
$uid = (int)p('user_id'); $bid = (int)p('business_id');
if ($uid && $bid) {
qrun("INSERT OR IGNORE INTO business_owners(user_id,business_id)VALUES(?,?)",[$uid,$bid]);
flash('Owner assigned!','success');
}
}
if ($act === 'remove') {
qrun("DELETE FROM business_owners WHERE user_id=? AND business_id=?",[(int)p('user_id'),(int)p('business_id')]);
flash('Owner removed.','success');
}
go('/admin/owners.php'.($_GET?'?'.http_build_query($_GET):''));
}
$filterUser = iget('user_id');
$filterBiz = iget('business_id');
$users = qall("SELECT id,username FROM users WHERE is_active=1 ORDER BY username");
$bizs = qall("SELECT id,name FROM businesses WHERE is_active=1 ORDER BY name");
$owners = qall("SELECT bo.*,u.username,b.name biz_name FROM business_owners bo JOIN users u ON u.id=bo.user_id JOIN businesses b ON b.id=bo.business_id ORDER BY b.name,u.username");
?>
<?php adminTitle('Assign Business Owners','Link user accounts to businesses so they can manage listings, post alerts, and update menus.') ?>
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title">🔑 ASSIGN OWNER TO BUSINESS</div>
<form method="POST">
<?=csrfField()?><input type="hidden" name="_act" value="assign">
<div class="form-row">
<div class="fg">
<label class="flabel">USER</label>
<select name="user_id" class="fselect" required>
<option value="">— Select user —</option>
<?php foreach($users as $u): ?><option value="<?=$u['id']?>"<?=$filterUser===$u['id']?' selected':''?>><?=e($u['username'])?></option><?php endforeach; ?>
</select>
</div>
<div class="fg">
<label class="flabel">BUSINESS</label>
<select name="business_id" class="fselect" required>
<option value="">— Select business —</option>
<?php foreach($bizs as $b): ?><option value="<?=$b['id']?>"<?=$filterBiz===$b['id']?' selected':''?>><?=e($b['name'])?></option><?php endforeach; ?>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary btn-sm">Assign Owner</button>
</form>
</div>
<div class="admin-sec-title">CURRENT OWNER ASSIGNMENTS (<?=count($owners)?>)</div>
<?php if($owners): ?>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>BUSINESS</th><th>OWNER / USER</th><th></th></tr></thead>
<tbody>
<?php foreach($owners as $o): ?>
<tr>
<td class="td-name"><a href="/business.php?slug=<?=e(qval("SELECT slug FROM businesses WHERE id=?",[$o['business_id']]))?>" target="_blank"><?=e($o['biz_name'])?></a></td>
<td><?=e($o['username'])?></td>
<td>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="remove"><input type="hidden" name="user_id" value="<?=$o['user_id']?>"><input type="hidden" name="business_id" value="<?=$o['business_id']?>"><button class="btn btn-xs btn-danger" data-confirm="Remove <?=e($o['username'])?> as owner of <?=e($o['biz_name'])?>?">Remove</button></form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="empty" style="padding:2rem"><div class="empty-ico">🔑</div><h3>No owner assignments yet</h3></div>
<?php endif; ?>
<?php include __DIR__.'/admin_footer.php'; ?>
+60
View File
@@ -0,0 +1,60 @@
<?php
$adminTitle = 'Listing Reports';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'close') { qrun("UPDATE reports SET status='closed',admin_notes=? WHERE id=?",[ps('notes'),(int)p('id')]); flash('Report closed.','success'); }
if ($act === 'delete') { qrun("DELETE FROM reports WHERE id=?",[(int)p('id')]); flash('Report deleted.','success'); }
go('/admin/reports.php');
}
$filter = gs('filter') ?: 'open';
$where = $filter === 'all' ? "" : "WHERE r.status='open'";
$reports = qall("SELECT r.*,b.name biz,b.slug biz_slug,u.username reporter FROM reports r JOIN businesses b ON b.id=r.business_id JOIN users u ON u.id=r.user_id $where ORDER BY r.created_at DESC");
?>
<?php adminTitle('Listing Correction Reports','User-submitted corrections and issues for business listings.') ?>
<div style="display:flex;gap:.4rem;margin-bottom:1rem">
<a href="/admin/reports.php?filter=open" class="btn btn-xs<?=$filter==='open'?' btn-primary':' btn-outline'?>">Open</a>
<a href="/admin/reports.php?filter=all" class="btn btn-xs<?=$filter==='all'?' btn-primary':' btn-outline'?>">All</a>
</div>
<?php if($reports): ?>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>BUSINESS</th><th>REPORTER</th><th>TYPE</th><th>MESSAGE</th><th>STATUS</th><th>DATE</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($reports as $r): ?>
<tr>
<td><a href="/business.php?slug=<?=e($r['biz_slug'])?>" target="_blank" class="td-name"><?=e($r['biz'])?></a></td>
<td><?=e($r['reporter'])?></td>
<td><span class="badge badge-gold"><?=e($r['type'])?></span></td>
<td style="font-size:.82rem;max-width:250px"><?=e(substr($r['message'],0,100)).(strlen($r['message'])>100?'…':'')?></td>
<td><?=$r['status']==='open'?'<span class="badge badge-red">Open</span>':'<span class="badge badge-green">Closed</span>'?></td>
<td style="font-size:.8rem"><?=ago($r['created_at'])?></td>
<td>
<?php if($r['status']==='open'): ?>
<details><summary class="btn btn-xs btn-outline" style="list-style:none;cursor:pointer">Close</summary>
<div style="background:var(--bg4);border:1px solid var(--bdr);border-radius:4px;padding:.75rem;margin-top:.4rem;min-width:220px">
<div style="font-size:.78rem;color:var(--text-m);margin-bottom:.5rem"><?=e($r['message'])?></div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="close"><input type="hidden" name="id" value="<?=$r['id']?>">
<textarea name="notes" class="ftextarea" style="min-height:55px;font-size:.8rem" placeholder="Admin notes (optional)…"></textarea>
<button type="submit" class="btn btn-xs btn-success" style="margin-top:.4rem">Mark Closed</button>
</form>
</div>
</details>
<?php else: ?>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$r['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete report?">🗑️</button></form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="empty" style="padding:2rem"><div class="empty-ico">✅</div><h3>No <?=$filter==='open'?'open':''?> reports</h3></div>
<?php endif; ?>
<?php include __DIR__.'/admin_footer.php'; ?>
+49
View File
@@ -0,0 +1,49 @@
<?php
$adminTitle = 'Reviews';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'approve') { qrun("UPDATE reviews SET is_approved=1 WHERE id=?",[(int)p('id')]); flash('Review approved.','success'); }
if ($act === 'hide') { qrun("UPDATE reviews SET is_approved=0 WHERE id=?",[(int)p('id')]); flash('Review hidden.','success'); }
if ($act === 'delete') { qrun("DELETE FROM reviews WHERE id=?",[(int)p('id')]); flash('Review deleted.','success'); }
go('/admin/reviews.php');
}
$page = max(1,iget('page')); $perPage = 30;
$total = (int)qval("SELECT COUNT(*) FROM reviews");
$pages = max(1,(int)ceil($total/$perPage));
$page = min($page,$pages);
$offset = ($page-1)*$perPage;
$reviews = qall("SELECT r.*,u.username,b.name biz_name,b.slug biz_slug FROM reviews r JOIN users u ON u.id=r.user_id JOIN businesses b ON b.id=r.business_id ORDER BY r.created_at DESC LIMIT $perPage OFFSET $offset");
?>
<?php adminTitle('Manage Reviews','Approve, hide, or delete user reviews.') ?>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>BUSINESS</th><th>USER</th><th>RATING</th><th>TITLE</th><th>STATUS</th><th>DATE</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($reviews as $r): ?>
<tr>
<td><a href="/business.php?slug=<?=e($r['biz_slug'])?>" target="_blank" class="td-name"><?=e($r['biz_name'])?></a></td>
<td><?=e($r['username'])?></td>
<td><?=starDisplay((float)$r['rating'])?></td>
<td style="font-size:.82rem;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?=e($r['title']?:$r['body'])?></td>
<td><?=$r['is_approved']?'<span class="badge badge-green">Visible</span>':'<span class="badge badge-gray">Hidden</span>'?></td>
<td style="font-size:.8rem"><?=ago($r['created_at'])?></td>
<td>
<div class="flex-row" style="gap:.3rem">
<?php if(!$r['is_approved']): ?><form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="approve"><input type="hidden" name="id" value="<?=$r['id']?>"><button class="btn btn-xs btn-success">Approve</button></form>
<?php else: ?><form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="hide"><input type="hidden" name="id" value="<?=$r['id']?>"><button class="btn btn-xs btn-secondary">Hide</button></form><?php endif; ?>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$r['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete this review?">🗑️</button></form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if($pages>1): ?>
<div style="display:flex;gap:.5rem;margin-top:1rem;flex-wrap:wrap"><?php for($i=1;$i<=$pages;$i++): ?><a href="?page=<?=$i?>" class="btn btn-sm<?=$i===$page?' btn-primary':' btn-outline'?>"><?=$i?></a><?php endfor; ?></div>
<?php endif; ?>
<?php include __DIR__.'/admin_footer.php'; ?>
+123
View File
@@ -0,0 +1,123 @@
<?php
$adminTitle = 'Site Settings';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'settings') {
setSetting('site_name', ps('site_name'));
setSetting('site_tagline', ps('site_tagline'));
setSetting('contact_email', ps('contact_email'));
setSetting('registration_enabled',p('registration_enabled','0'));
setSetting('events_require_approval',p('events_require_approval','0'));
flash('Settings saved!','success');
go('/admin/settings.php');
}
// Change admin password
if ($act === 'change_password') {
$pw = ps('new_password');
$pw2 = ps('confirm_password');
if (strlen($pw) < 6) { flash('Password must be at least 6 characters.','error'); go('/admin/settings.php'); }
if ($pw !== $pw2) { flash('Passwords do not match.','error'); go('/admin/settings.php'); }
qrun("UPDATE users SET password=? WHERE id=?",[password_hash($pw,PASSWORD_BCRYPT),uid()]);
flash('Password updated successfully!','success');
go('/admin/settings.php');
}
}
$regEnabled = setting('registration_enabled','1');
$evtApproval = setting('events_require_approval','1');
$siteName = setting('site_name','Discover Keyser WV');
$siteTagline = setting('site_tagline','The Friendliest City in the U.S.A.');
$contactEmail = setting('contact_email','info@discoverkeyser.com');
$me = qone("SELECT * FROM users WHERE id=?",[uid()]);
?>
<?php adminTitle('Site Settings') ?>
<div class="g2" style="gap:1.5rem">
<!-- General settings -->
<div>
<div class="ibox" style="border-color:var(--gold-d)">
<div class="ibox-title">🌐 GENERAL SETTINGS</div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="settings">
<div class="fg"><label class="flabel">SITE NAME</label><input type="text" name="site_name" class="finput" value="<?=e($siteName)?>"></div>
<div class="fg"><label class="flabel">SITE TAGLINE</label><input type="text" name="site_tagline" class="finput" value="<?=e($siteTagline)?>"></div>
<div class="fg"><label class="flabel">CONTACT EMAIL</label><input type="email" name="contact_email" class="finput" value="<?=e($contactEmail)?>"></div>
<hr class="divider" style="margin:1rem 0">
<div class="fg">
<label class="flabel">USER REGISTRATION</label>
<div style="display:flex;gap:1rem;margin-top:.4rem">
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.9rem">
<input type="radio" name="registration_enabled" value="1"<?=$regEnabled==='1'?' checked':''?>>
<span style="color:var(--green-l)">✅ Enabled — users can register</span>
</label>
</div>
<div style="display:flex;gap:1rem;margin-top:.4rem">
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.9rem">
<input type="radio" name="registration_enabled" value="0"<?=$regEnabled==='0'?' checked':''?>>
<span style="color:var(--red-l)">🔒 Disabled — registration closed</span>
</label>
</div>
<div class="fhint">When disabled, the Sign Up link is hidden and registration page shows a message.</div>
</div>
<div class="fg">
<label class="flabel">EVENT SUBMISSIONS</label>
<div style="margin-top:.4rem">
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.9rem;margin-bottom:.35rem">
<input type="radio" name="events_require_approval" value="1"<?=$evtApproval==='1'?' checked':''?>>
<span>Require admin approval before events go public</span>
</label>
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.9rem">
<input type="radio" name="events_require_approval" value="0"<?=$evtApproval==='0'?' checked':''?>>
<span>Auto-approve all submitted events</span>
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
</div>
</div>
<!-- Admin password change -->
<div>
<div class="ibox" style="border-color:var(--gold-d)">
<div class="ibox-title">🔑 CHANGE YOUR ADMIN PASSWORD</div>
<div class="irow"><span class="irow-i">👤</span><span class="irow-v"><?=e($me['username'])?> <span class="badge badge-gold">ADMIN</span></span></div>
<div class="irow" style="margin-bottom:1rem"><span class="irow-i">📅</span><span class="irow-v">Last login: <?=$me['last_login']?ago($me['last_login']):'—'?></span></div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="change_password">
<div class="fg"><label class="flabel">NEW PASSWORD *</label><input type="password" name="new_password" class="finput" required autocomplete="new-password"><div class="fhint">Minimum 6 characters.</div></div>
<div class="fg"><label class="flabel">CONFIRM NEW PASSWORD *</label><input type="password" name="confirm_password" class="finput" required autocomplete="new-password"></div>
<button type="submit" class="btn btn-primary">Update Password</button>
</form>
</div>
<!-- DB info -->
<div class="ibox">
<div class="ibox-title">📊 DATABASE INFO</div>
<?php
$counts = [
'Businesses' => qval("SELECT COUNT(*) FROM businesses"),
'Users' => qval("SELECT COUNT(*) FROM users"),
'Reviews' => qval("SELECT COUNT(*) FROM reviews"),
'Events' => qval("SELECT COUNT(*) FROM events"),
'Attractions'=> qval("SELECT COUNT(*) FROM attractions"),
'Alerts' => qval("SELECT COUNT(*) FROM alerts"),
'Reports' => qval("SELECT COUNT(*) FROM reports"),
'Menu Sections'=> qval("SELECT COUNT(*) FROM menu_sections"),
'Menu Items' => qval("SELECT COUNT(*) FROM menu_items"),
];
foreach ($counts as $lbl => $n):
?><div class="irow"><span class="irow-i">📌</span><span class="irow-v" style="display:flex;justify-content:space-between;width:100%"><span><?=e($lbl)?></span><strong style="color:var(--gold)"><?=$n?></strong></span></div><?php endforeach; ?>
<?php if(file_exists(DB_FILE)): ?>
<div class="irow"><span class="irow-i">💾</span><span class="irow-v" style="display:flex;justify-content:space-between;width:100%"><span>DB File Size</span><strong style="color:var(--gold)"><?=number_format(filesize(DB_FILE)/1024,1)?> KB</strong></span></div>
<?php endif; ?>
</div>
</div>
</div>
<?php include __DIR__.'/admin_footer.php'; ?>
+109
View File
@@ -0,0 +1,109 @@
<?php
$adminTitle = 'Users';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
$act = p('_act');
if ($act === 'add') {
$un = ps('username'); $em = ps('email'); $pw = ps('password');
if (!$un || !$pw) { flash('Username and password required.','error'); go('/admin/users.php'); }
if (qval("SELECT id FROM users WHERE username=?",[$un])) { flash('Username taken.','error'); go('/admin/users.php'); }
qrun("INSERT INTO users(username,email,password,is_admin,is_active)VALUES(?,?,?,?,?)",[$un,$em?:null,password_hash($pw,PASSWORD_BCRYPT),(int)p('is_admin',0),(int)p('is_active',1)]);
flash('User added!','success'); go('/admin/users.php');
}
if ($act === 'edit') {
$id = (int)p('id');
$un = ps('username'); $em = ps('email'); $pw = ps('password');
if (!$un) { flash('Username required.','error'); go('/admin/users.php'); }
if (qval("SELECT id FROM users WHERE username=? AND id!=?",[$un,$id])) { flash('Username taken.','error'); go('/admin/users.php'); }
if ($pw) {
qrun("UPDATE users SET username=?,email=?,password=?,is_admin=?,is_active=? WHERE id=?",[$un,$em?:null,password_hash($pw,PASSWORD_BCRYPT),(int)p('is_admin',0),(int)p('is_active',1),$id]);
} else {
qrun("UPDATE users SET username=?,email=?,is_admin=?,is_active=? WHERE id=?",[$un,$em?:null,(int)p('is_admin',0),(int)p('is_active',1),$id]);
}
flash('User updated!','success'); go('/admin/users.php');
}
if ($act === 'delete') {
$id = (int)p('id');
if ($id === uid()) { flash('You cannot delete your own account.','error'); go('/admin/users.php'); }
qrun("DELETE FROM users WHERE id=?",[$id]);
flash('User deleted.','success'); go('/admin/users.php');
}
if ($act === 'toggle_active') {
$id = (int)p('id');
if ($id !== uid()) { qrun("UPDATE users SET is_active=1-is_active WHERE id=?",[$id]); }
go('/admin/users.php');
}
}
$editId = iget('edit');
$editing = $editId ? qone("SELECT * FROM users WHERE id=?",[$editId]) : null;
$page = max(1,iget('page')); $perPage = 30;
$total = (int)qval("SELECT COUNT(*) FROM users");
$pages = max(1,(int)ceil($total/$perPage));
$page = min($page,$pages);
$offset = ($page-1)*$perPage;
$users = qall("SELECT * FROM users ORDER BY is_admin DESC,username ASC LIMIT $perPage OFFSET $offset");
?>
<?php adminTitle($editing ? 'Edit User: '.e($editing['username']) : 'Manage Users') ?>
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title"><?=$editing ? '✏️ EDIT USER: '.strtoupper(e($editing['username'])) : ' ADD NEW USER'?></div>
<form method="POST">
<?=csrfField()?>
<input type="hidden" name="_act" value="<?=$editing?'edit':'add'?>">
<?php if($editing): ?><input type="hidden" name="id" value="<?=$editing['id']?>"><?php endif; ?>
<div class="form-row">
<div class="fg"><label class="flabel">USERNAME *</label><input type="text" name="username" class="finput" value="<?=e($editing['username']??'')?>" required></div>
<div class="fg"><label class="flabel">EMAIL</label><input type="email" name="email" class="finput" value="<?=e($editing['email']??'')?>"></div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel"><?=$editing?'NEW PASSWORD (leave blank to keep)':'PASSWORD *'?></label><input type="password" name="password" class="finput" <?=$editing?'':'required'?>><div class="fhint">Minimum 6 characters.</div></div>
<div class="fg" style="display:grid;grid-template-columns:1fr 1fr;gap:.75rem">
<div class="fg"><label class="flabel">ROLE</label><select name="is_admin" class="fselect"><option value="0"<?=!($editing['is_admin']??0)?' selected':''?>>Regular User</option><option value="1"<?=($editing['is_admin']??0)?' selected':''?>>Administrator</option></select></div>
<div class="fg"><label class="flabel">STATUS</label><select name="is_active" class="fselect"><option value="1"<?=($editing['is_active']??1)?' selected':''?>>Active</option><option value="0"<?=isset($editing)&&!$editing['is_active']?' selected':''?>>Suspended</option></select></div>
</div>
</div>
<div class="flex-row">
<button type="submit" class="btn btn-primary"><?=$editing?'Update User':'Add User'?></button>
<?php if($editing): ?><a href="/admin/users.php" class="btn btn-secondary">Cancel</a><?php endif; ?>
</div>
</form>
</div>
<div class="admin-sec-title">ALL USERS (<?=$total?>)</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>USERNAME</th><th>EMAIL</th><th>ROLE</th><th>STATUS</th><th>JOINED</th><th>LAST LOGIN</th><th>ACTIONS</th></tr></thead>
<tbody>
<?php foreach($users as $u): ?>
<tr>
<td class="td-name"><?=e($u['username'])?></td>
<td style="font-size:.8rem"><?=e($u['email']??'—')?></td>
<td><?=$u['is_admin']?'<span class="badge badge-gold">ADMIN</span>':'<span class="badge badge-gray">User</span>'?></td>
<td><?=$u['is_active']?'<span class="badge badge-green">Active</span>':'<span class="badge badge-red">Suspended</span>'?></td>
<td style="font-size:.8rem"><?=fdate($u['created_at'])?></td>
<td style="font-size:.8rem"><?=$u['last_login']?ago($u['last_login']):'Never'?></td>
<td>
<div class="flex-row" style="gap:.3rem">
<a href="/admin/users.php?edit=<?=$u['id']?>" class="btn btn-xs btn-outline">✏️ Edit</a>
<a href="/admin/owners.php?user_id=<?=$u['id']?>" class="btn btn-xs btn-secondary">🔑 Assign</a>
<?php if($u['id']!==uid()): ?>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="toggle_active"><input type="hidden" name="id" value="<?=$u['id']?>"><button class="btn btn-xs btn-secondary"><?=$u['is_active']?'Suspend':'Restore'?></button></form>
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete"><input type="hidden" name="id" value="<?=$u['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete user '<?=e($u['username'])?>'?">🗑️</button></form>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if($pages>1): ?>
<div style="display:flex;gap:.5rem;margin-top:1rem;flex-wrap:wrap">
<?php for($i=1;$i<=$pages;$i++): ?><a href="?page=<?=$i?>" class="btn btn-sm<?=$i===$page?' btn-primary':' btn-outline'?>"><?=$i?></a><?php endfor; ?>
</div>
<?php endif; ?>
<?php include __DIR__.'/admin_footer.php'; ?>