BROOKO icon
BROOKO UK NETWORK
Where code meets creativity & adventure
File viewer

time_clock_records_panel.php

Type
php
Size
10.54 KB
Modified
15 May
time_clock_records_panel.php 10.54 KB
<?php
require_once __DIR__ . '/../lib/feature_modules.php';
wp_feature_ensure_time_clock_schema();
global $pdo;

$date = trim((string)($_GET['date'] ?? date('Y-m-d')));
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
    $date = date('Y-m-d');
}

$rows = [];
$photosByEntry = [];
$totals = ['entries' => 0, 'open' => 0, 'closed' => 0, 'hours' => 0.0, 'photos' => 0];

if (isset($pdo) && $pdo instanceof PDO && wp_db_table_exists('time_clock_entries')) {
    $stmt = $pdo->prepare("SELECT * FROM time_clock_entries WHERE DATE(clock_in_at) = ? ORDER BY clock_in_at DESC, id DESC");
    $stmt->execute([$date]);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    if ($rows && wp_db_table_exists('time_clock_photos')) {
        $ids = array_map(static fn($r) => (int)$r['id'], $rows);
        $place = implode(',', array_fill(0, count($ids), '?'));
        $ps = $pdo->prepare("SELECT * FROM time_clock_photos WHERE clock_entry_id IN ($place) ORDER BY created_at DESC, id DESC");
        $ps->execute($ids);
        foreach (($ps->fetchAll(PDO::FETCH_ASSOC) ?: []) as $photo) {
            $photosByEntry[(int)$photo['clock_entry_id']][] = $photo;
        }
    }
}

function wp_clock_admin_label(string $key): string {
    $map = [
        'vehicle_walkaround' => 'Vehicle walk-around',
        'keys_docs_checked' => 'Keys / docs / fuel card',
        'assigned_van_confirmed' => 'Assigned van / team',
        'ppe_equipment_ready' => 'PPE / equipment ready',
        'van_parked' => 'Van parked correctly',
        'locked_disclock' => 'Locked / disc lock',
        'inventory_checked' => 'Inventory checked',
        'fuel_card_returned' => 'Fuel card returned',
        'fuel_added' => 'Fuel question answered',
        'job_area_cleared' => 'Job area cleared',
        'equipment_returned' => 'Equipment returned',
        'issues_reported' => 'Issues reported',
    ];
    return $map[$key] ?? ucwords(str_replace('_', ' ', trim($key)));
}

function wp_clock_decode_meta(?string $json): array {
    if (!$json) return [];
    $decoded = json_decode((string)$json, true);
    return is_array($decoded) ? $decoded : [];
}

function wp_clock_render_check_cards(array $checks, string $title): void {
    if (empty($checks)) return;
    echo '<div class="clock-record-block"><strong>' . e($title) . '</strong><div class="clock-record-checks">';
    foreach ($checks as $key => $passed) {
        echo '<div class="clock-record-check"><span>' . e(wp_clock_admin_label((string)$key)) . '</span><strong>' . (!empty($passed) ? 'Done' : 'Missing') . '</strong></div>';
    }
    echo '</div></div>';
}

?>
<style>
.clock-record-card{margin-bottom:16px;}
.clock-record-top{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:flex-start;}
.clock-record-badges{display:flex;gap:8px;flex-wrap:wrap;}
.clock-record-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin-top:14px;}
.clock-record-stat{border:1px solid var(--border);border-radius:14px;background:rgba(255,255,255,.025);padding:12px;}
.clock-record-stat span{display:block;color:var(--text-muted);font-size:.82rem;text-transform:uppercase;letter-spacing:.04em;margin-bottom:4px;}
.clock-record-stat strong{font-size:1rem;}
.clock-record-block{margin-top:14px;}
.clock-record-checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px;margin-top:10px;}
.clock-record-check{display:flex;justify-content:space-between;gap:10px;border:1px solid var(--border);border-radius:12px;background:rgba(255,255,255,.02);padding:10px 12px;}
.clock-photo-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px;margin-top:10px;}
.clock-photo-card{border:1px solid var(--border);border-radius:12px;background:rgba(255,255,255,.02);padding:12px;}
@media(max-width:700px){.clock-record-top{display:grid;}.content-header form{width:100%;}.content-header form .btn{width:100%;}.clock-record-grid{grid-template-columns:1fr;}}
</style>
<div class="content-header">
    <div>
        <h1 class="content-title">⏱️ <?= e($pageTitle ?? 'Time Clock Records') ?></h1>
        <p class="content-subtitle"><?= e($pageSubtitle ?? 'Review clock in/out times, worked hours, role checks, odometer readings and uploaded photos for the selected day.') ?></p>
    </div>
    <form method="GET" class="d-flex gap-2 align-items-end" style="flex-wrap:wrap;">
        <div>
            <label class="form-label" for="clockDate">Date</label>
            <input class="form-control" id="clockDate" type="date" name="date" value="<?= e($date) ?>">
        </div>
        <button type="submit" class="btn btn-primary">Load Day</button>
    </form>
</div>

<?php if (empty($rows)): ?>
    <div class="card"><div class="card-body"><p class="text-muted" style="margin:0;">No time clock entries were found for <?= e(date('d/m/Y', strtotime($date))) ?>.</p></div></div>
<?php else: ?>
    <?php foreach ($rows as $entry): ?>
        <?php
        $totals['entries']++;
        $isOpen = empty($entry['clock_out_at']) || strtolower((string)($entry['status'] ?? '')) === 'clocked_in';
        if ($isOpen) $totals['open']++; else $totals['closed']++;
        $hours = 0.0;
        if (!empty($entry['clock_in_at']) && !empty($entry['clock_out_at'])) {
            $hours = max(0, (strtotime((string)$entry['clock_out_at']) - strtotime((string)$entry['clock_in_at']))) / 3600;
            $totals['hours'] += $hours;
        }
        $entryPhotos = $photosByEntry[(int)$entry['id']] ?? [];
        $totals['photos'] += count($entryPhotos);
        $startMeta = wp_clock_decode_meta($entry['start_meta_json'] ?? null);
        $endMeta = wp_clock_decode_meta($entry['end_meta_json'] ?? null);
        ?>
        <div class="card clock-record-card">
            <div class="card-body">
                <div class="clock-record-top">
                    <div>
                        <h3 style="margin:0 0 6px 0;"><?= e((string)($entry['display_name'] ?: $entry['username'])) ?></h3>
                        <div class="text-muted"><?= e(ucwords(str_replace('-', ' ', (string)($entry['role_name'] ?: 'staff')))) ?> · <?= e((string)$entry['username']) ?></div>
                    </div>
                    <div class="clock-record-badges">
                        <span class="badge <?= $isOpen ? 'badge-warning' : 'badge-success' ?>"><?= $isOpen ? 'Open shift' : 'Closed shift' ?></span>
                        <span class="badge badge-secondary"><?= e(number_format($hours, 2)) ?>h</span>
                        <span class="badge badge-secondary"><?= count($entryPhotos) ?> photo<?= count($entryPhotos) === 1 ? '' : 's' ?></span>
                    </div>
                </div>

                <div class="clock-record-grid">
                    <div class="clock-record-stat"><span>Clock in</span><strong><?= e(!empty($entry['clock_in_at']) ? date('d/m/Y H:i', strtotime((string)$entry['clock_in_at'])) : '—') ?></strong></div>
                    <div class="clock-record-stat"><span>Clock out</span><strong><?= e(!empty($entry['clock_out_at']) ? date('d/m/Y H:i', strtotime((string)$entry['clock_out_at'])) : '—') ?></strong></div>
                    <div class="clock-record-stat"><span>Van / assignment</span><strong><?= e((string)($entry['van_label'] ?? $startMeta['van_label'] ?? '—')) ?></strong></div>
                    <div class="clock-record-stat"><span>Van reg</span><strong><?= e((string)($entry['van_reg'] ?? $startMeta['van_reg'] ?? '—')) ?></strong></div>
                    <div class="clock-record-stat"><span>Working with</span><strong><?= e((string)($entry['partner_name'] ?? $startMeta['partner_name'] ?? '—')) ?></strong></div>
                    <div class="clock-record-stat"><span>Odometer in</span><strong><?= e((string)($entry['mileage_in'] ?? $startMeta['mileage_in'] ?? '—')) ?></strong></div>
                    <div class="clock-record-stat"><span>Odometer out</span><strong><?= e((string)($entry['mileage_out'] ?? $endMeta['mileage_out'] ?? '—')) ?></strong></div>
                    <div class="clock-record-stat"><span>Status</span><strong><?= e((string)($entry['status'] ?: '—')) ?></strong></div>
                </div>

                <?php if (!empty($startMeta['note']) || !empty($entry['notes'])): ?>
                    <div class="clock-record-block"><strong>Notes</strong><p class="text-muted" style="margin:8px 0 0;"><?= e((string)($entry['notes'] ?: ($startMeta['note'] ?? '—'))) ?></p></div>
                <?php endif; ?>

                <?php wp_clock_render_check_cards((array)($startMeta['checks'] ?? []), 'Start-of-shift checks'); ?>
                <?php wp_clock_render_check_cards((array)($endMeta['checks'] ?? []), 'End-of-shift checks'); ?>

                <?php if (!empty($entryPhotos)): ?>
                    <div class="clock-record-block">
                        <strong>Uploaded photos</strong>
                        <div class="clock-photo-grid">
                            <?php foreach ($entryPhotos as $photo): ?>
                                <div class="clock-photo-card">
                                    <div><?= e((string)$photo['file_name']) ?></div>
                                    <div class="text-muted"><?= e((string)$photo['phase']) ?> · <?= e(date('d/m H:i', strtotime((string)$photo['created_at']))) ?></div>
                                    <?php if (!empty($photo['onedrive_web_url'])): ?><a class="btn btn-sm btn-secondary" style="margin-top:8px;" target="_blank" rel="noopener" href="<?= e((string)$photo['onedrive_web_url']) ?>">Open photo</a><?php endif; ?>
                                </div>
                            <?php endforeach; ?>
                        </div>
                    </div>
                <?php endif; ?>
            </div>
        </div>
    <?php endforeach; ?>

    <div class="card">
        <div class="card-body" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;">
            <div><div class="text-muted">Entries</div><div style="font-size:1.5rem;font-weight:800;"><?= (int)$totals['entries'] ?></div></div>
            <div><div class="text-muted">Open shifts</div><div style="font-size:1.5rem;font-weight:800;"><?= (int)$totals['open'] ?></div></div>
            <div><div class="text-muted">Closed shifts</div><div style="font-size:1.5rem;font-weight:800;"><?= (int)$totals['closed'] ?></div></div>
            <div><div class="text-muted">Worked hours</div><div style="font-size:1.5rem;font-weight:800;"><?= e(number_format($totals['hours'], 2)) ?></div></div>
            <div><div class="text-muted">Photos</div><div style="font-size:1.5rem;font-weight:800;"><?= (int)$totals['photos'] ?></div></div>
        </div>
    </div>
<?php endif; ?>