clock.php
29.23 KB
<?php
require_once __DIR__ . '/../bootstrap.php';
require_once __DIR__ . '/../lib/feature_modules.php';
requireAuth();
global $pdo;
$pageTitle = 'Shift Clock';
$currentUser = getCurrentUser();
$userId = (int)($_SESSION['user_id'] ?? 0);
$wpRole = (string)($currentUser['role'] ?? ($_SESSION['user_role'] ?? ''));
wp_feature_ensure_time_clock_schema();
$roleMode = wp_feature_clock_role_mode($wpRole);
$startConfig = wp_feature_clock_start_requirements($wpRole);
$checkoutConfig = wp_feature_clock_checkout_requirements($wpRole);
$clockStatus = wp_clock_status($userId);
$message = null;
$messageType = 'success';
function wp_clock_post_text(string $key): string {
return trim((string)($_POST[$key] ?? ''));
}
function wp_clock_uploaded_count(?array $files): int {
if (!is_array($files) || !isset($files['name']) || !is_array($files['name'])) return 0;
$count = 0;
foreach ($files['name'] as $name) {
if (trim((string)$name) !== '') $count++;
}
return $count;
}
function wp_clock_collect_checks(array $config, string $fieldName = 'check'): array {
$checks = [];
foreach (($config['items'] ?? []) as $key => $label) {
$checks[$key] = !empty($_POST[$fieldName][$key]);
}
return $checks;
}
function wp_clock_missing_checks(array $config, array $checks): array {
$errors = [];
foreach (($config['items'] ?? []) as $key => $label) {
if (empty($checks[$key])) {
$errors[] = $label . ' must be ticked.';
}
}
return $errors;
}
function wp_clock_int_or_null(string $value): ?int {
$value = trim($value);
if ($value === '') return null;
if (!preg_match('/^\d+$/', $value)) return null;
return (int)$value;
}
function wp_clock_config_label(array $config, string $key, string $fallback): string {
return (string)($config[$key] ?? $fallback);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($pdo) && $pdo instanceof PDO && wp_db_table_exists('time_clock_entries')) {
$action = (string)($_POST['action'] ?? '');
if ($action === 'clock_in' && ($clockStatus['status'] ?? '') === 'not_clocked_in') {
$formErrors = [];
$startChecks = wp_clock_collect_checks($startConfig, 'start_check');
$formErrors = array_merge($formErrors, wp_clock_missing_checks($startConfig, $startChecks));
$vanLabel = wp_clock_post_text('van_label');
$vanReg = wp_clock_post_text('van_reg');
$partnerName = wp_clock_post_text('partner_name');
$mileageInRaw = wp_clock_post_text('mileage_in');
$mileageIn = wp_clock_int_or_null($mileageInRaw);
$startNotes = wp_clock_post_text('start_note');
$startPhotos = $_FILES['start_photos'] ?? null;
$startPhotoRequested = wp_clock_uploaded_count(is_array($startPhotos) ? $startPhotos : null);
$startPhotoMin = (int)($startConfig['photo_min'] ?? 0);
if (!empty($startConfig['require_van']) && $vanLabel === '') {
$formErrors[] = 'Van number / assignment is required.';
}
if (!empty($startConfig['require_partner']) && $partnerName === '') {
$formErrors[] = wp_clock_config_label($startConfig, 'partner_label', 'Working with') . ' is required.';
}
if (!empty($startConfig['require_van_reg']) && $vanReg === '') {
$formErrors[] = 'Van registration is required.';
}
if (!empty($startConfig['require_mileage']) && $mileageIn === null) {
$formErrors[] = 'Start odometer reading is required.';
}
if ($startPhotoRequested < $startPhotoMin) {
$formErrors[] = 'Drivers must upload at least ' . $startPhotoMin . ' start-of-shift photos.';
}
$perm = (string)($startConfig['permission'] ?? '');
if ($perm !== '' && !hasPermission($perm) && !isAdminRole()) {
$formErrors[] = 'Your account is missing the permission needed to start this shift type.';
}
$photoPerm = (string)($startConfig['photo_permission'] ?? '');
if ($startPhotoRequested > 0 && $photoPerm !== '' && !hasPermission($photoPerm) && !isAdminRole()) {
$formErrors[] = 'Your account is missing permission to upload shift photos.';
}
if ($formErrors) {
$message = implode(' ', array_values(array_unique($formErrors)));
$messageType = 'error';
} else {
try {
$startMeta = [
'mode' => $roleMode,
'checks' => $startChecks,
'note' => $startNotes,
'van_label' => $vanLabel,
'van_reg' => $vanReg,
'partner_name' => $partnerName,
'mileage_in' => $mileageIn,
'photos_expected' => $startPhotoRequested,
'source' => 'database_shift_clock',
];
$stmt = $pdo->prepare("INSERT INTO time_clock_entries (user_id, username, display_name, role_name, van_label, van_reg, partner_name, mileage_in, clock_in_at, status, ip_address, notes, start_meta_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), 'clocked_in', ?, ?, ?)");
$stmt->execute([
$userId,
(string)($currentUser['username'] ?? ''),
(string)($currentUser['display_name'] ?? ''),
$wpRole,
$vanLabel !== '' ? $vanLabel : null,
$vanReg !== '' ? $vanReg : null,
$partnerName !== '' ? $partnerName : null,
$mileageIn,
$_SERVER['REMOTE_ADDR'] ?? null,
$startNotes !== '' ? $startNotes : null,
json_encode($startMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
]);
$entryId = (int)$pdo->lastInsertId();
$uploadErrors = [];
$savedPhotos = 0;
if ($startPhotoRequested > 0 && is_array($startPhotos)) {
$uploadResult = wp_feature_clock_upload_files($entryId, $startPhotos, 'clock_in');
$savedPhotos = (int)($uploadResult['saved'] ?? 0);
$uploadErrors = $uploadResult['errors'] ?? [];
if ($savedPhotos < $startPhotoMin) {
$uploadErrors[] = 'The required start-of-shift photos could not be saved.';
}
}
if ($uploadErrors) {
if (wp_db_table_exists('time_clock_photos')) { $pdo->prepare("DELETE FROM time_clock_photos WHERE clock_entry_id = ?")->execute([$entryId]); }
$pdo->prepare("DELETE FROM time_clock_entries WHERE id = ? AND user_id = ?")->execute([$entryId, $userId]);
$message = implode(' ', array_values(array_unique($uploadErrors)));
$messageType = 'error';
} else {
logActivity('clock.in', 'time_clock_entries', $entryId, 'Started shift');
$message = 'Shift started successfully.';
}
$clockStatus = wp_clock_status($userId);
} catch (Throwable $e) {
$message = 'Clock in failed. Please try again.';
$messageType = 'error';
}
}
}
if ($action === 'clock_out' && ($clockStatus['status'] ?? '') === 'clocked_in' && !empty($clockStatus['entry']['id'])) {
$entryId = (int)$clockStatus['entry']['id'];
$checkoutChecks = wp_clock_collect_checks($checkoutConfig, 'check');
$formErrors = wp_clock_missing_checks($checkoutConfig, $checkoutChecks);
$existingEntry = $clockStatus['entry'] ?? [];
$vanLabel = wp_clock_post_text('van_label');
$vanReg = wp_clock_post_text('van_reg');
$partnerName = wp_clock_post_text('partner_name');
if ($vanLabel === '' && !empty($existingEntry['van_label'])) $vanLabel = (string)$existingEntry['van_label'];
if ($vanReg === '' && !empty($existingEntry['van_reg'])) $vanReg = (string)$existingEntry['van_reg'];
if ($partnerName === '' && !empty($existingEntry['partner_name'])) $partnerName = (string)$existingEntry['partner_name'];
$mileageOutRaw = wp_clock_post_text('mileage_out');
$mileageOut = wp_clock_int_or_null($mileageOutRaw);
$endNotes = wp_clock_post_text('end_note');
$endPhotos = $_FILES['end_photos'] ?? null;
$endPhotoRequested = wp_clock_uploaded_count(is_array($endPhotos) ? $endPhotos : null);
$endPhotoMin = (int)($checkoutConfig['photo_min'] ?? 0);
if (!empty($checkoutConfig['require_van']) && $vanLabel === '') {
$formErrors[] = 'Van number / assignment is required before ending this shift.';
}
if (!empty($checkoutConfig['require_partner']) && $partnerName === '') {
$formErrors[] = wp_clock_config_label($checkoutConfig, 'partner_label', 'Working with') . ' is required before ending this shift.';
}
if (!empty($checkoutConfig['require_mileage']) && $mileageOut === null) {
$formErrors[] = 'End odometer reading is required.';
}
if ($endPhotoRequested < $endPhotoMin) {
$formErrors[] = 'Drivers must upload at least ' . $endPhotoMin . ' end-of-shift photos.';
}
$perm = (string)($checkoutConfig['permission'] ?? '');
if ($perm !== '' && !hasPermission($perm) && !isAdminRole()) {
$formErrors[] = 'Your account is missing the permission needed to complete this shift check.';
}
$photoPerm = (string)($checkoutConfig['photo_permission'] ?? '');
if ($endPhotoRequested > 0 && $photoPerm !== '' && !hasPermission($photoPerm) && !isAdminRole()) {
$formErrors[] = 'Your account is missing permission to upload shift photos.';
}
if ($formErrors) {
$message = implode(' ', array_values(array_unique($formErrors)));
$messageType = 'error';
} else {
try {
$endMeta = [
'mode' => $roleMode,
'checks' => $checkoutChecks,
'note' => $endNotes,
'van_label' => $vanLabel,
'van_reg' => $vanReg,
'partner_name' => $partnerName,
'mileage_out' => $mileageOut,
'photos_expected' => $endPhotoRequested,
'source' => 'database_shift_clock',
];
$stmt = $pdo->prepare("UPDATE time_clock_entries SET van_label = COALESCE(NULLIF(?, ''), van_label), van_reg = COALESCE(NULLIF(?, ''), van_reg), partner_name = COALESCE(NULLIF(?, ''), partner_name), mileage_out = ?, clock_out_at = NOW(), status = 'clocked_out', notes = ?, end_meta_json = ? WHERE id = ? AND user_id = ?");
$stmt->execute([
$vanLabel,
$vanReg,
$partnerName,
$mileageOut,
$endNotes !== '' ? $endNotes : null,
json_encode($endMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
$entryId,
$userId,
]);
$uploadErrors = [];
$savedPhotos = 0;
if ($endPhotoRequested > 0 && is_array($endPhotos)) {
$uploadResult = wp_feature_clock_upload_files($entryId, $endPhotos, 'clock_out');
$savedPhotos = (int)($uploadResult['saved'] ?? 0);
$uploadErrors = $uploadResult['errors'] ?? [];
if ($savedPhotos < $endPhotoMin) {
$uploadErrors[] = 'The required end-of-shift photos could not be saved.';
}
}
if ($uploadErrors) {
$message = implode(' ', array_values(array_unique($uploadErrors)));
$messageType = 'error';
if ($endPhotoMin > 0) {
$undo = $pdo->prepare("UPDATE time_clock_entries SET clock_out_at = NULL, status = 'clocked_in' WHERE id = ? AND user_id = ?");
$undo->execute([$entryId, $userId]);
}
} else {
logActivity('clock.out', 'time_clock_entries', $entryId, 'Ended shift');
$message = 'Shift ended successfully.';
}
$clockStatus = wp_clock_status($userId);
} catch (Throwable $e) {
$message = 'Clock out failed. Please try again.';
$messageType = 'error';
}
}
}
}
$rotaToday = null;
try {
$rotaRows = function_exists('wp_feature_fetch_rota_for_current_user') ? wp_feature_fetch_rota_for_current_user(date('Y-m-d'), 2) : [];
$rotaToday = $rotaRows[0] ?? null;
} catch (Throwable $e) {
$rotaToday = null;
}
$clockRequired = wp_clock_required_for_current_user();
$clockLabel = 'Not clocked in yet';
$clockMeta = $clockRequired ? 'You need to start your shift before you can use the app.' : 'Shift clocking is available for your role, but it is optional.';
if (($clockStatus['status'] ?? '') === 'clocked_in') {
$clockLabel = 'Currently on shift';
$clockMeta = 'Shift started at ' . date('H:i', strtotime((string)$clockStatus['entry']['clock_in_at']));
} elseif (($clockStatus['status'] ?? '') === 'shift_ended') {
$clockLabel = 'Shift completed';
$in = !empty($clockStatus['entry']['clock_in_at']) ? date('H:i', strtotime((string)$clockStatus['entry']['clock_in_at'])) : '—';
$out = !empty($clockStatus['entry']['clock_out_at']) ? date('H:i', strtotime((string)$clockStatus['entry']['clock_out_at'])) : '—';
$hours = (!empty($clockStatus['entry']['clock_in_at']) && !empty($clockStatus['entry']['clock_out_at'])) ? number_format(max(0, (strtotime((string)$clockStatus['entry']['clock_out_at']) - strtotime((string)$clockStatus['entry']['clock_in_at']))) / 3600, 2) : '0.00';
$clockMeta = 'Today: ' . $in . ' to ' . $out . ' (' . $hours . 'h)';
}
$entry = $clockStatus['entry'] ?? [];
$defaultVan = (string)($entry['van_label'] ?? ($rotaToday['van_label'] ?? ''));
$defaultReg = (string)($entry['van_reg'] ?? '');
$defaultPartner = (string)($entry['partner_name'] ?? '');
$modeName = $roleMode === 'driver' ? 'Driver' : ($roleMode === 'porter' ? 'Porter' : 'General');
$extraHead = '<style>
.shift-clock-page{--clock-gap:18px;}
.shift-clock-grid{display:grid;grid-template-columns:minmax(0,1.12fr) minmax(320px,.88fr);gap:var(--clock-gap);align-items:start;}
.shift-clock-hero,.shift-clock-panel{border:1px solid var(--border);border-radius:22px;background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.018));box-shadow:0 18px 45px rgba(0,0,0,.18);}
.shift-clock-hero{padding:22px;}
.shift-clock-panel{padding:18px;}
.shift-clock-status{display:inline-flex;align-items:center;gap:10px;padding:9px 13px;border-radius:999px;background:rgba(255,255,255,.05);border:1px solid var(--border);font-weight:850;}
.shift-clock-dot{width:11px;height:11px;border-radius:999px;background:#ef4444;box-shadow:0 0 0 6px rgba(239,68,68,.10);}
.shift-clock-dot.is-on{background:#22c55e;box-shadow:0 0 0 6px rgba(34,197,94,.14);}
.shift-clock-title{font-size:clamp(1.45rem,3vw,2.35rem);line-height:1;margin:16px 0 8px 0;font-weight:900;}
.shift-clock-meta{color:var(--text-muted);font-size:1rem;margin:0;}
.shift-clock-action{margin-top:18px;display:grid;gap:16px;}
.shift-clock-form{display:grid;gap:16px;}
.shift-clock-section{border:1px solid var(--border);border-radius:18px;background:rgba(0,0,0,.12);padding:16px;display:grid;gap:14px;}
.shift-clock-section h3{margin:0;font-size:1.05rem;font-weight:900;}
.shift-clock-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
.shift-clock-field{display:grid;gap:7px;}
.shift-clock-field label,.shift-clock-check strong{font-weight:800;}
.shift-clock-help{font-size:.88rem;color:var(--text-muted);}
.shift-clock-checks{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;}
.shift-clock-check{display:flex;gap:10px;align-items:flex-start;border:1px solid var(--border);border-radius:14px;background:rgba(255,255,255,.025);padding:11px 12px;}
.shift-clock-check input{width:18px;height:18px;margin-top:2px;accent-color:var(--accent);}
.shift-clock-photos{display:grid;gap:8px;padding:13px;border:1px dashed rgba(255,140,26,.45);border-radius:16px;background:rgba(255,140,26,.055);}
.shift-clock-photos input{padding:10px;background:rgba(0,0,0,.18);border-radius:12px;border:1px solid var(--border);}
.shift-clock-buttons{display:flex;gap:12px;flex-wrap:wrap;align-items:center;}
.shift-clock-list{display:grid;gap:10px;}
.shift-clock-list-item{display:flex;justify-content:space-between;gap:14px;align-items:flex-start;padding:11px 12px;border:1px solid var(--border);border-radius:14px;background:rgba(255,255,255,.025);}
.shift-clock-list-item span{color:var(--text-muted);}
.shift-clock-list-item strong{text-align:right;}
.shift-clock-pill{display:inline-flex;align-items:center;gap:8px;border-radius:999px;padding:8px 11px;background:rgba(255,140,26,.10);color:var(--accent);border:1px solid rgba(255,140,26,.25);font-weight:800;}
.shift-clock-note{min-height:108px;}
.shift-clock-alert{margin-bottom:16px;}
@media (max-width:1050px){.shift-clock-grid{grid-template-columns:1fr;}.shift-clock-panel{order:2;}}
@media (max-width:720px){.shift-clock-hero,.shift-clock-panel{border-radius:18px;padding:15px;}.shift-clock-fields,.shift-clock-checks{grid-template-columns:1fr;}.shift-clock-buttons .btn{width:100%;justify-content:center;}.shift-clock-list-item{display:grid;grid-template-columns:1fr;}.shift-clock-list-item strong{text-align:left;}.content-header{display:block;}}
</style>';
include __DIR__ . '/../partials/header.php';
function wp_clock_render_checks(array $config, string $namePrefix): void {
if (empty($config['items']) || !is_array($config['items'])) return;
echo '<div class="shift-clock-section"><h3>' . e((string)$config['title']) . '</h3><div class="shift-clock-checks">';
foreach ($config['items'] as $key => $label) {
echo '<label class="shift-clock-check"><input type="checkbox" name="' . e($namePrefix) . '[' . e((string)$key) . ']" value="1"><span><strong>' . e((string)$label) . '</strong><br><small class="shift-clock-help">Required before continuing.</small></span></label>';
}
echo '</div></div>';
}
?>
<div class="shift-clock-page">
<div class="content-header">
<div>
<h1 class="content-title">⏱️ Shift Clock</h1>
<p class="content-subtitle"><?= e($clockRequired ? 'Start your shift to unlock the rest of the app.' : 'Start or end your shift here. Admin and Director accounts can still use the app without clocking in.') ?></p>
</div>
<span class="shift-clock-pill"><?= e($modeName) ?> workflow</span>
</div>
<?php if ($message): ?>
<div class="alert alert-<?= $messageType === 'error' ? 'error' : 'success' ?> shift-clock-alert"><?= e($message) ?></div>
<?php endif; ?>
<div class="shift-clock-grid">
<section class="shift-clock-hero">
<div class="shift-clock-status">
<span class="shift-clock-dot <?= ($clockStatus['status'] ?? '') === 'clocked_in' ? 'is-on' : '' ?>"></span>
<span><?= e($clockLabel) ?></span>
</div>
<h2 class="shift-clock-title"><?= e((string)($currentUser['display_name'] ?? 'User')) ?></h2>
<p class="shift-clock-meta"><?= e(ucwords(str_replace('-', ' ', (string)($currentUser['role'] ?? 'staff')))) ?> · <?= e($clockMeta) ?></p>
<div class="shift-clock-action">
<?php if (($clockStatus['status'] ?? '') === 'not_clocked_in'): ?>
<form method="POST" enctype="multipart/form-data" class="shift-clock-form">
<input type="hidden" name="action" value="clock_in">
<div class="shift-clock-section">
<h3><?= e((string)$startConfig['title']) ?></h3>
<div class="shift-clock-fields">
<?php if (!empty($startConfig['require_van'])): ?>
<div class="shift-clock-field">
<label for="van_label">Van / assignment <?= !empty($startConfig['require_van']) ? '*' : '' ?></label>
<input class="form-control" id="van_label" name="van_label" value="<?= e($defaultVan) ?>" placeholder="Van number or route">
</div>
<?php endif; ?>
<?php if (!empty($startConfig['require_van_reg'])): ?>
<div class="shift-clock-field">
<label for="van_reg">Van registration *</label>
<input class="form-control" id="van_reg" name="van_reg" value="<?= e($defaultReg) ?>" placeholder="Registration">
</div>
<?php endif; ?>
<?php if (!empty($startConfig['require_partner'])): ?>
<div class="shift-clock-field">
<label for="partner_name"><?= e(wp_clock_config_label($startConfig, 'partner_label', 'Working with')) ?> *</label>
<input class="form-control" id="partner_name" name="partner_name" value="<?= e($defaultPartner) ?>" placeholder="Name">
</div>
<?php endif; ?>
<?php if (!empty($startConfig['require_mileage'])): ?>
<div class="shift-clock-field">
<label for="mileage_in">Start odometer *</label>
<input class="form-control" id="mileage_in" name="mileage_in" type="number" inputmode="numeric" min="0" placeholder="e.g. 45823">
</div>
<?php endif; ?>
</div>
</div>
<?php wp_clock_render_checks($startConfig, 'start_check'); ?>
<?php if ((int)($startConfig['photo_min'] ?? 0) > 0): ?>
<div class="shift-clock-photos">
<label class="form-label" for="start_photos">Start-of-shift photos *</label>
<input id="start_photos" type="file" name="start_photos[]" accept="image/*" multiple>
<div class="shift-clock-help">Drivers must upload at least <?= (int)$startConfig['photo_min'] ?> start photos. These are saved to OneDrive when configured.</div>
</div>
<?php endif; ?>
<div class="shift-clock-field">
<label for="start_note">Start note</label>
<textarea class="form-control shift-clock-note" id="start_note" name="start_note" rows="3" placeholder="Anything to record before starting?"></textarea>
</div>
<div class="shift-clock-buttons">
<button type="submit" class="btn btn-primary">Start Shift</button>
<a href="<?= e(app_url('logout')) ?>" class="btn btn-secondary">Logout</a>
</div>
</form>
<?php elseif (($clockStatus['status'] ?? '') === 'clocked_in'): ?>
<form method="POST" enctype="multipart/form-data" class="shift-clock-form">
<input type="hidden" name="action" value="clock_out">
<div class="shift-clock-section">
<h3>End shift details</h3>
<div class="shift-clock-fields">
<?php if (!empty($checkoutConfig['require_van'])): ?>
<div class="shift-clock-field">
<label for="co_van_label">Van / assignment *</label>
<input class="form-control" id="co_van_label" name="van_label" value="<?= e($defaultVan) ?>" placeholder="Van number or route">
</div>
<?php endif; ?>
<?php if (!empty($checkoutConfig['require_partner'])): ?>
<div class="shift-clock-field">
<label for="co_partner_name"><?= e(wp_clock_config_label($checkoutConfig, 'partner_label', 'Working with')) ?> *</label>
<input class="form-control" id="co_partner_name" name="partner_name" value="<?= e($defaultPartner) ?>" placeholder="Name">
</div>
<?php endif; ?>
<?php if (!empty($checkoutConfig['require_mileage'])): ?>
<div class="shift-clock-field">
<label for="mileage_out">End odometer *</label>
<input class="form-control" id="mileage_out" name="mileage_out" type="number" inputmode="numeric" min="0" placeholder="e.g. 45991">
</div>
<?php endif; ?>
</div>
</div>
<?php wp_clock_render_checks($checkoutConfig, 'check'); ?>
<?php if ((int)($checkoutConfig['photo_min'] ?? 0) > 0): ?>
<div class="shift-clock-photos">
<label class="form-label" for="end_photos">End-of-shift photos *</label>
<input id="end_photos" type="file" name="end_photos[]" accept="image/*" multiple>
<div class="shift-clock-help">Drivers must upload at least <?= (int)$checkoutConfig['photo_min'] ?> end photos before ending the shift.</div>
</div>
<?php endif; ?>
<div class="shift-clock-field">
<label for="end_note">End note</label>
<textarea class="form-control shift-clock-note" id="end_note" name="end_note" placeholder="Anything to record before you end the shift?"></textarea>
</div>
<div class="shift-clock-buttons">
<button type="submit" class="btn btn-secondary">End Shift</button>
<a href="<?= e(app_url()) ?>" class="btn btn-outline-light">Continue to App</a>
</div>
</form>
<?php else: ?>
<div class="shift-clock-buttons">
<a href="<?= e(app_url()) ?>" class="btn btn-primary">Continue to App</a>
<a href="<?= e(app_url('logout')) ?>" class="btn btn-secondary">Logout</a>
</div>
<?php endif; ?>
</div>
</section>
<aside class="shift-clock-panel">
<h3 style="margin-top:0;">Today's rota</h3>
<?php if ($rotaToday): ?>
<div class="shift-clock-list">
<div class="shift-clock-list-item"><span>Route</span><strong><?= e((string)($rotaToday['route_name'] ?: 'Not set')) ?></strong></div>
<div class="shift-clock-list-item"><span>Van</span><strong><?= e((string)($rotaToday['van_label'] ?: 'Not set')) ?></strong></div>
<div class="shift-clock-list-item"><span>Role</span><strong><?= e((string)($rotaToday['role_label'] ?: 'Not set')) ?></strong></div>
<div class="shift-clock-list-item"><span>Notes</span><strong><?= e((string)($rotaToday['notes'] ?: '—')) ?></strong></div>
</div>
<?php else: ?>
<p class="text-muted" style="margin:0 0 12px 0;">No rota entry has been assigned for you today.</p>
<?php endif; ?>
<hr>
<h3>Role requirements</h3>
<div class="shift-clock-list">
<?php if ($roleMode === 'driver'): ?>
<div class="shift-clock-list-item"><span>Start</span><strong>Van, porter/helper, reg, odometer and <?= (int)$startConfig['photo_min'] ?> photos</strong></div>
<div class="shift-clock-list-item"><span>End</span><strong>Checks, odometer and <?= (int)$checkoutConfig['photo_min'] ?> photos</strong></div>
<?php elseif ($roleMode === 'porter'): ?>
<div class="shift-clock-list-item"><span>Start</span><strong>Assigned van/team and PPE confirmation</strong></div>
<div class="shift-clock-list-item"><span>End</span><strong>Clear-up, equipment and issue-report confirmation</strong></div>
<?php else: ?>
<div class="shift-clock-list-item"><span>Start / End</span><strong>Simple shift clock with notes</strong></div>
<?php endif; ?>
<div class="shift-clock-list-item"><span>Review</span><strong>Managers and admins can view daily records</strong></div>
</div>
</aside>
</div>
</div>
<?php include __DIR__ . '/../partials/footer.php'; ?>