347 lines
16 KiB
PHP
347 lines
16 KiB
PHP
<?php
|
|
$adminTitle = 'Owner Edit Requests';
|
|
include __DIR__.'/admin_header.php';
|
|
|
|
// ── Field labels & apply logic ────────────────────────────
|
|
$fieldLabels = [
|
|
'description' => 'Description',
|
|
'address' => 'Address',
|
|
'phone' => 'Phone',
|
|
'email' => 'Email',
|
|
'website' => 'Website',
|
|
'video_url' => 'Video URL',
|
|
'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']]);
|
|
}
|
|
} elseif ($field === 'video_url') {
|
|
if (validBusinessVideoUrl($val)) {
|
|
qrun("UPDATE businesses SET video_url=? 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]);
|
|
}
|
|
} elseif ($field === 'video_url') {
|
|
if (validBusinessVideoUrl($val)) {
|
|
qrun("UPDATE businesses SET video_url=? 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 & 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'; ?>
|