rota.php
10.53 KB
<?php
require_once __DIR__ . '/../bootstrap.php';
requireAuth();
requirePermission('rota.view');
$pageTitle = 'Rota';
$canManage = isAdminRole() || hasPermission('rota.manage');
// ── Helpers ───────────────────────────────────────────────────────────
function wp_ordinal(int $n): string {
$nAbs = abs($n);
$mod100 = $nAbs % 100;
if ($mod100 >= 11 && $mod100 <= 13) return $n . 'th';
return match ($nAbs % 10) {
1 => $n . 'st',
2 => $n . 'nd',
3 => $n . 'rd',
default => $n . 'th',
};
}
function wp_week_start(string $anchor): int {
$ts = strtotime($anchor) ?: time();
$dow = (int)date('N', $ts); // 1 (Mon) .. 7 (Sun)
return (int)strtotime('-' . ($dow - 1) . ' days', $ts);
}
// ── Params ────────────────────────────────────────────────────────────
$anchor = $_GET['date'] ?? date('Y-m-d');
$span = (int)($_GET['span'] ?? 2);
if ($span < 1) $span = 1;
if ($span > 4) $span = 4;
$print = isset($_GET['print']) && $_GET['print'] === '1';
$baseWeekStartTs = wp_week_start($anchor);
$weekStarts = [];
for ($i = 0; $i < $span; $i++) {
$weekStarts[] = date('Y-m-d', strtotime('+' . ($i * 7) . ' days', $baseWeekStartTs));
}
// Navigation jumps by the visible span
$jumpDays = $span * 7;
$prevAnchor = date('Y-m-d', strtotime('-' . $jumpDays . ' days', $baseWeekStartTs));
$nextAnchor = date('Y-m-d', strtotime('+' . $jumpDays . ' days', $baseWeekStartTs));
// ── Load lanes (columns) ──────────────────────────────────────────────
$crewLanes = [];
$officeLanes = [];
$dbOk = true;
try {
$lanes = $pdo->query("SELECT id, code, label, lane_type, sort_order FROM rota_lanes WHERE is_active=1 ORDER BY sort_order ASC")
->fetchAll(PDO::FETCH_ASSOC);
foreach ($lanes as $l) {
if (($l['lane_type'] ?? 'crew') === 'office') $officeLanes[] = $l; else $crewLanes[] = $l;
}
} catch (Throwable $e) {
$dbOk = false;
}
// ── Load week publish flags + entries ─────────────────────────────────
$weeksMeta = []; // week_start => [is_published]
$entries = []; // week_start => entry_date => lane_id => value
if ($dbOk) {
try {
$in = implode(',', array_fill(0, count($weekStarts), '?'));
$stmtW = $pdo->prepare("SELECT week_start, is_published FROM rota_weeks WHERE week_start IN ($in)");
$stmtW->execute($weekStarts);
foreach ($stmtW->fetchAll(PDO::FETCH_ASSOC) as $w) {
$weeksMeta[(string)$w['week_start']] = ['is_published' => (int)$w['is_published'] === 1];
}
$stmtE = $pdo->prepare("SELECT week_start, entry_date, lane_id, value FROM rota_entries WHERE week_start IN ($in)");
$stmtE->execute($weekStarts);
foreach ($stmtE->fetchAll(PDO::FETCH_ASSOC) as $r) {
$ws = (string)$r['week_start'];
$d = (string)$r['entry_date'];
$lid = (int)$r['lane_id'];
$entries[$ws] ??= [];
$entries[$ws][$d] ??= [];
$entries[$ws][$d][$lid] = (string)($r['value'] ?? '');
}
} catch (Throwable $e) {
$dbOk = false;
}
}
// Staff should only see published weeks
$visibleWeekStarts = [];
foreach ($weekStarts as $ws) {
$pub = (bool)($weeksMeta[$ws]['is_published'] ?? false);
if ($canManage || $pub) $visibleWeekStarts[] = $ws;
}
// Extra head: print layout
if ($print) {
$extraHead = '<style>
@media print {
.sidebar, .page-header-simple, .header-nav, .content-header, .btn, .no-print { display:none !important; }
.main-area { margin:0 !important; }
.main-content { padding:0 !important; }
.app-container { display:block !important; }
}
.rota-sheet-header{display:flex;align-items:center;justify-content:space-between;gap:14px;margin:4px 0 18px 0;}
.rota-sheet-title{font-weight:900;font-size:1.35rem;letter-spacing:.2px;}
.rota-sheet-sub{margin-top:4px;color:var(--muted-text,#9aa3ad);font-size:.95rem;}
.rota-logo{width:86px;height:86px;object-fit:contain;border-radius:12px;background:rgba(255,255,255,.04);padding:8px;}
.rota-week{margin-bottom:28px;}
.rota-week-heading{font-weight:900;text-transform:uppercase;font-size:.85rem;letter-spacing:.4px;margin:0 0 6px 0;}
.rota-table{width:100%;border-collapse:collapse;}
.rota-table th,.rota-table td{border:1px solid rgba(255,145,0,.35);padding:6px 8px;font-size:.86rem;vertical-align:top;}
.rota-table thead th{background:rgba(255,145,0,.10);font-weight:900;}
.rota-table tbody tr:nth-child(even) td{background:rgba(255,145,0,.06);}
.rota-day{font-weight:900;text-transform:uppercase;white-space:nowrap;width:118px;}
.rota-date{font-weight:900;white-space:nowrap;width:60px;text-align:center;}
.rota-cell{white-space:pre-wrap;}
</style>';
}
include __DIR__ . '/../partials/header.php';
$companyName = getSystemInfo('company_name', 'WorkersPanel');
$logoFiles = glob(__DIR__ . '/../assets/images/logo.*') ?: [];
$logoSrc = !empty($logoFiles) ? '/assets/images/' . basename($logoFiles[0]) : '';
if ($logoSrc) {
$disk = __DIR__ . '/../assets/images/' . basename($logoFiles[0]);
$logoSrc .= '?v=' . ((int)@filemtime($disk) ?: time());
}
?>
<div class="content-header no-print">
<div>
<h1 class="content-title">🗓️ Rota</h1>
<p class="content-subtitle">Weekly staff rota sheet (no calendar link).</p>
</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<a class="btn btn-secondary" href="/rota?date=<?= e($prevAnchor) ?>&span=<?= (int)$span ?>">← Prev</a>
<a class="btn btn-secondary" href="/rota?date=<?= e(date('Y-m-d')) ?>&span=<?= (int)$span ?>">Today</a>
<a class="btn btn-secondary" href="/rota?date=<?= e($nextAnchor) ?>&span=<?= (int)$span ?>">Next →</a>
<a class="btn btn-primary" href="/rota?date=<?= e($weekStarts[0]) ?>&span=<?= (int)$span ?>&print=1" target="_blank">🖨 Print</a>
<?php if ($canManage && hasPermission('management.access')): ?>
<a class="btn btn-secondary" href="/management/rota?date=<?= e($weekStarts[0]) ?>&span=<?= (int)$span ?>">⚙️ Manage</a>
<?php endif; ?>
</div>
</div>
<?php if (!$dbOk): ?>
<div class="alert alert-warning">
Rota tables are not available in your database yet. Ask an admin to apply the latest update.
</div>
<?php endif; ?>
<div class="rota-sheet-header">
<div>
<div class="rota-sheet-title"><?= e($companyName) ?> Staff Rota</div>
<div class="rota-sheet-sub">Week commencing Monday · UK format (dd/mm/yyyy)</div>
</div>
<?php if ($logoSrc): ?>
<img class="rota-logo" src="<?= e($logoSrc) ?>" alt="<?= e($companyName) ?>">
<?php endif; ?>
</div>
<?php if (empty($visibleWeekStarts)): ?>
<div class="card">
<div class="card-body">
<div class="text-muted">No rota published yet.</div>
</div>
</div>
<?php else: ?>
<?php foreach ($visibleWeekStarts as $ws):
$wsTs = strtotime($ws) ?: time();
$pub = (bool)($weeksMeta[$ws]['is_published'] ?? false);
$weekLabel = date('d/m/Y', $wsTs);
$weekEndLabel = date('d/m/Y', strtotime('+6 days', $wsTs));
$weekEntries = $entries[$ws] ?? [];
?>
<div class="rota-week">
<div style="display:flex;justify-content:space-between;align-items:flex-end;gap:12px;flex-wrap:wrap;">
<div>
<div class="rota-week-heading">WEEK COMMENCING: <?= e($weekLabel) ?></div>
<div class="text-muted" style="font-size:.9rem;"><?= e($weekLabel) ?> → <?= e($weekEndLabel) ?></div>
</div>
<?php if ($canManage): ?>
<?php if ($pub): ?>
<span class="badge badge-success">Published</span>
<?php else: ?>
<span class="badge badge-muted">Draft</span>
<?php endif; ?>
<?php endif; ?>
</div>
<div class="table-wrap" style="margin-top:10px;">
<table class="rota-table">
<thead>
<tr>
<th colspan="2">DATE</th>
<?php foreach ($crewLanes as $l): ?>
<th><?= ($l['label'] !== '' ? e($l['label']) : ' ') ?></th>
<?php endforeach; ?>
<?php foreach ($officeLanes as $l): ?>
<th><?= ($l['label'] !== '' ? e($l['label']) : ' ') ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php for ($d = 0; $d < 7; $d++):
$date = date('Y-m-d', strtotime('+' . $d . ' days', $wsTs));
$dayName = strtoupper(date('l', strtotime($date)));
$dayNum = (int)date('j', strtotime($date));
$dateLabel = wp_ordinal($dayNum);
?>
<tr>
<td class="rota-day"><?= e($dayName) ?></td>
<td class="rota-date"><?= e($dateLabel) ?></td>
<?php foreach ($crewLanes as $l):
$lid = (int)$l['id'];
$val = (string)($weekEntries[$date][$lid] ?? '');
?>
<td class="rota-cell"><?= e($val) ?></td>
<?php endforeach; ?>
<?php foreach ($officeLanes as $l):
$lid = (int)$l['id'];
$val = (string)($weekEntries[$date][$lid] ?? '');
?>
<td class="rota-cell"><?= e($val) ?></td>
<?php endforeach; ?>
</tr>
<?php endfor; ?>
</tbody>
</table>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?php include __DIR__ . '/../partials/footer.php'; ?>