Files
theoldfitz/admin/hours.php
T
David Dyer c7b836bcf2 Refactor admin pages to use shared head and navigation components
- Created `admin-head.php` for consistent <head> markup across admin pages.
- Created `admin-nav.php` for shared navigation bar, reducing duplication.
- Updated all admin pages (account, hours, index, login, menu, users) to utilize the new components.
- Added security headers to `auth.php` and `index.php` for improved security.
- Introduced `security-headers.php` to manage security headers across the application.
- Moved inline styles from admin pages to SCSS files to comply with Content Security Policy (CSP).
- Normalized file permissions during build process to prevent permission issues on deployment.
2026-10-25 16:06:15 -04:00

183 lines
6.9 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/assets.php';
require_once __DIR__ . '/../inc/admin-head.php';
require_once __DIR__ . '/../inc/admin-nav.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">
<?php render_admin_head('Hours'); ?>
<body>
<?php render_admin_nav($user, 'hours'); ?>
<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 hours-note">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 hours-preview-intro">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>