Add pub hours admin panel, rebuild announcements/users lists on CSS Grid
- Add a Hours admin panel (admin/hours.php, admin/hours.js) for editing the weekly open/close schedule and an optional per-day kitchen closing time, with a live preview of how it'll read on the public site - Store hours in a new pub_hours table (inc/db.php), seeded with the site's existing Thu-Sat/9pm-kitchen schedule; inc/hours.php formats and groups it for display (day ranges, kitchen-close note, footer summary) - Wire the public Visit card and both site footers (index.php, menu.php) to read hours from that data instead of hardcoded copy - Rebuild the Announcements and Users admin tables onto the same CSS Grid list pattern already used for the Menu (admin-table-actions -> row-actions), and make the Announcements status toggle an in-place AJAX action like the menu item's (admin/index.js) - Add the Hours tab to admin nav across all four admin pages Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fd928a24cf
commit
eab20899de
14 files changed
+1224
-441
No files matched your search
+205
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../inc/auth.php';
|
||||
require_once __DIR__ . '/../inc/hours.php';
|
||||
|
||||
require_auth();
|
||||
$user = get_logged_in_user();
|
||||
|
||||
$errorMsg = '';
|
||||
|
||||
/**
|
||||
* Reads a day's submitted fields back into the same shape get_all_hours()
|
||||
* rows use, so the form can re-render exactly what was typed (rather than
|
||||
* falling back to the last-saved DB values) when validation fails.
|
||||
*/
|
||||
function hours_day_from_post(array $dayData): array {
|
||||
return [
|
||||
'is_open' => !empty($dayData['is_open']) ? 1 : 0,
|
||||
'open_time' => normalize_time_value($dayData['open_time'] ?? null),
|
||||
'close_time' => normalize_time_value($dayData['close_time'] ?? null),
|
||||
'kitchen_close_time' => normalize_time_value($dayData['kitchen_close_time'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* A day marked "Open" needs both ends of its range set — everything else
|
||||
* (closed days, the optional kitchen closing time) is fine left blank.
|
||||
*/
|
||||
function validate_hours_input(array $daysInput): ?string {
|
||||
for ($d = 0; $d <= 6; $d++) {
|
||||
$day = hours_day_from_post(is_array($daysInput[$d] ?? null) ? $daysInput[$d] : []);
|
||||
if ($day['is_open'] && (!$day['open_time'] || !$day['close_time'])) {
|
||||
return HOURS_DAY_NAMES[$d] . ' is marked open but is missing an opening or closing time.';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$submittedHours = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
|
||||
$errorMsg = 'Session expired or invalid security token. Please try again.';
|
||||
} elseif (($_POST['action'] ?? '') === 'save_hours') {
|
||||
$daysInput = is_array($_POST['day'] ?? null) ? $_POST['day'] : [];
|
||||
|
||||
if ($error = validate_hours_input($daysInput)) {
|
||||
$errorMsg = $error;
|
||||
$submittedHours = [];
|
||||
for ($d = 0; $d <= 6; $d++) {
|
||||
$submittedHours[$d] = hours_day_from_post(is_array($daysInput[$d] ?? null) ? $daysInput[$d] : []);
|
||||
}
|
||||
} else {
|
||||
save_all_hours($daysInput);
|
||||
header('Location: hours.php?saved=1');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hoursByDay = $submittedHours ?? get_all_hours();
|
||||
$justSaved = !$_POST && isset($_GET['saved']);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Hours | The Old Fitz Admin</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400&family=Karla:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<div class="container admin-nav-inner">
|
||||
<a href="index.php" class="admin-brand">
|
||||
<img src="../assets/knot.png" alt="" class="knot">
|
||||
<span>The Old Fitz Admin</span>
|
||||
</a>
|
||||
<div class="admin-nav-links">
|
||||
<a href="index.php">Announcements</a>
|
||||
<a href="menu.php">Menu</a>
|
||||
<a href="users.php">Users</a>
|
||||
<a href="hours.php" class="active">Hours</a>
|
||||
</div>
|
||||
<div class="admin-user">
|
||||
<span>Logged in as <strong><?= htmlspecialchars($user['username']) ?></strong></span>
|
||||
<a href="../" target="_blank" rel="noopener">View Public Site ↗</a>
|
||||
<a href="logout.php">Sign Out</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="dashboard-layout">
|
||||
<div class="container">
|
||||
<div class="dash-header">
|
||||
<p class="eyebrow">Operations</p>
|
||||
<h1>Pub Hours</h1>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($errorMsg)): ?>
|
||||
<div class="alert-error" role="alert"><?= htmlspecialchars($errorMsg) ?></div>
|
||||
<?php elseif ($justSaved): ?>
|
||||
<div class="alert-success" role="status">Hours saved — the public site is now showing the updated schedule.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<h2>
|
||||
<span>🕒 Weekly Schedule</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="hours.php">
|
||||
<input type="hidden" name="action" value="save_hours">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
|
||||
|
||||
<div class="hours-grid-wrap">
|
||||
<div class="hours-grid-head" aria-hidden="true">
|
||||
<span class="col-day">Day</span>
|
||||
<span class="col-hopen">Open</span>
|
||||
<span class="col-hopens">Opens</span>
|
||||
<span class="col-hcloses">Closes</span>
|
||||
<span class="col-hkitchen">Kitchen Closes</span>
|
||||
</div>
|
||||
<div class="hours-day-rows">
|
||||
<?php for ($d = 0; $d <= 6; $d++): ?>
|
||||
<?php $day = $hoursByDay[$d]; ?>
|
||||
<div class="hours-day-row" data-day="<?= $d ?>">
|
||||
<div data-label="Day"><strong><?= HOURS_DAY_NAMES[$d] ?></strong></div>
|
||||
<div data-label="Open">
|
||||
<label class="checkbox-label day-open-toggle">
|
||||
<input type="checkbox" name="day[<?= $d ?>][is_open]" value="1" <?= !empty($day['is_open']) ? 'checked' : '' ?>>
|
||||
Open
|
||||
</label>
|
||||
</div>
|
||||
<div data-label="Opens">
|
||||
<input type="time" name="day[<?= $d ?>][open_time]" class="form-control day-time-field" value="<?= htmlspecialchars($day['open_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div data-label="Closes">
|
||||
<input type="time" name="day[<?= $d ?>][close_time]" class="form-control day-time-field" value="<?= htmlspecialchars($day['close_time'] ?? '') ?>">
|
||||
</div>
|
||||
<div data-label="Kitchen Closes">
|
||||
<input type="time" name="day[<?= $d ?>][kitchen_close_time]" class="form-control day-time-field" value="<?= htmlspecialchars($day['kitchen_close_time'] ?? '') ?>">
|
||||
</div>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="muted field-hint" style="margin-top: 1.25rem;">Leave "Kitchen Closes" blank on a day if the kitchen doesn't have a separate cutoff from the bar. Special closures (holidays, weather) are handled on the <a href="index.php">Announcements</a> page instead of here.</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save Hours</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<h2>
|
||||
<span>👀 Public Site Preview</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p class="muted" style="margin-bottom: 1.25rem;">This is how the schedule above will read on the site once saved.</p>
|
||||
|
||||
<div class="hours-preview">
|
||||
<div class="hours-preview-card">
|
||||
<dl class="hours-list">
|
||||
<?php foreach (hours_card_rows($hoursByDay) as $row): ?>
|
||||
<div class="hours-row">
|
||||
<dt><?= htmlspecialchars($row['label']) ?></dt>
|
||||
<dd><?= htmlspecialchars($row['text']) ?></dd>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</dl>
|
||||
<?php $kitchenNote = hours_kitchen_note($hoursByDay); ?>
|
||||
<p class="note">
|
||||
<?php if ($kitchenNote): ?><strong><?= htmlspecialchars($kitchenNote) ?></strong> — bar snacks stay out later. <?php endif; ?>Holiday hours may vary; check our Facebook page for the latest.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hours-preview-footer">
|
||||
<p class="col-title">Hours</p>
|
||||
<div class="hours-mini">
|
||||
<?php foreach (hours_footer_lines($hoursByDay) as $line): ?>
|
||||
<span><?= htmlspecialchars($line) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="hours.js" defer></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in new issue
Block a user