39760-vm/dashboard.php
2026-04-21 09:45:25 +00:00

201 lines
9.9 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
require_once __DIR__ . '/app.php';
require_admin();
$statusFilter = normalize_request_status((string) ($_GET['status'] ?? '')) ?? '';
$searchQuery = normalize_request_search((string) ($_GET['q'] ?? ''));
$page = max(1, (int) ($_GET['page'] ?? 1));
$perPage = 10;
$totalRequests = count_requests($statusFilter !== '' ? $statusFilter : null, $searchQuery);
$totalPages = max(1, (int) ceil(max($totalRequests, 1) / $perPage));
if ($page > $totalPages) {
$page = $totalPages;
}
$offset = ($page - 1) * $perPage;
$requests = fetch_requests($statusFilter !== '' ? $statusFilter : null, $searchQuery, $perPage, $offset);
$metrics = dashboard_metrics();
$showingStart = $totalRequests > 0 ? $offset + 1 : 0;
$showingEnd = $totalRequests > 0 ? $offset + count($requests) : 0;
$hasActiveFilters = $statusFilter !== '' || $searchQuery !== '';
$buildDashboardUrl = static function (array $overrides = []) use ($statusFilter, $searchQuery, $page): string {
$params = [
'status' => $statusFilter,
'q' => $searchQuery,
'page' => $page,
];
foreach ($overrides as $key => $value) {
$params[$key] = $value;
}
foreach ($params as $key => $value) {
if ($value === '' || $value === null || ($key === 'page' && (int) $value <= 1)) {
unset($params[$key]);
}
}
$query = http_build_query($params);
return '/dashboard.php' . ($query !== '' ? '?' . $query : '');
};
render_head(project_name() . ' | Dashboard', 'Admin dashboard for reviewing incoming project requests.', true);
render_navbar('dashboard');
render_flash_toast();
?>
<main>
<section class="py-4 py-lg-5 border-bottom bg-white">
<div class="container">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-end gap-3 mb-4">
<div>
<div class="section-kicker">Dashboard</div>
<h1 class="h2 mb-2">Project request queue</h1>
<p class="text-secondary mb-0">Review the latest submissions, search contacts or companies, filter by status, and open each request detail page.</p>
</div>
<a class="btn btn-dark" href="/#request-form">Create another request</a>
</div>
<div class="row g-3">
<div class="col-sm-6 col-xl-3">
<div class="stat-card"><div class="small text-secondary">Total requests</div><div class="stat-value"><?= e((string) $metrics['total']) ?></div></div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="stat-card"><div class="small text-secondary">New</div><div class="stat-value"><?= e((string) $metrics['new']) ?></div></div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="stat-card"><div class="small text-secondary">Reviewed</div><div class="stat-value"><?= e((string) $metrics['reviewed']) ?></div></div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="stat-card"><div class="small text-secondary">Qualified</div><div class="stat-value"><?= e((string) $metrics['qualified']) ?></div></div>
</div>
</div>
</div>
</section>
<section class="py-4 py-lg-5" id="requests">
<div class="container">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3 mb-3">
<div>
<h2 class="h4 mb-1">All requests</h2>
<p class="text-secondary mb-0">Search by reference, name, email, company, or project type, then paginate through the queue.</p>
</div>
<div class="d-flex flex-wrap gap-2">
<a href="<?= e($buildDashboardUrl(['status' => '', 'page' => 1])) ?>" class="btn btn-sm <?= $statusFilter === '' ? 'btn-dark' : 'btn-outline-secondary' ?>">All</a>
<?php foreach (request_status_options() as $value => $label): ?>
<a href="<?= e($buildDashboardUrl(['status' => $value, 'page' => 1])) ?>" class="btn btn-sm <?= $statusFilter === $value ? 'btn-dark' : 'btn-outline-secondary' ?>"><?= e($label) ?></a>
<?php endforeach; ?>
</div>
</div>
<div class="panel-card mb-3">
<form method="get" class="row g-3 align-items-end" role="search">
<div class="col-lg-7">
<label class="form-label" for="q">Search requests</label>
<input class="form-control" id="q" name="q" type="search" value="<?= e($searchQuery) ?>" placeholder="Try REQ-0001, name, email, company, or project type">
</div>
<div class="col-md-4 col-lg-3">
<label class="form-label" for="status">Status</label>
<select class="form-select" id="status" name="status">
<option value="">All statuses</option>
<?php foreach (request_status_options() as $value => $label): ?>
<option value="<?= e($value) ?>" <?= $statusFilter === $value ? 'selected' : '' ?>><?= e($label) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-8 col-lg-2 d-grid d-md-flex gap-2">
<button class="btn btn-dark flex-fill" type="submit">Apply</button>
<?php if ($hasActiveFilters): ?>
<a class="btn btn-outline-secondary flex-fill" href="/dashboard.php">Reset</a>
<?php endif; ?>
</div>
</form>
</div>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-2 mb-3 small text-secondary">
<div>
Showing <?= e((string) $showingStart) ?><?= e((string) $showingEnd) ?> of <?= e((string) $totalRequests) ?> request<?= $totalRequests === 1 ? '' : 's' ?>.
</div>
<?php if ($hasActiveFilters): ?>
<div>Filtered results are based on your current search and status selection.</div>
<?php endif; ?>
</div>
<div class="panel-card p-0 overflow-hidden">
<?php if (!$requests): ?>
<div class="empty-state p-5 text-center">
<?php if ($hasActiveFilters): ?>
<h3 class="h5 mb-2">No matching requests</h3>
<p class="text-secondary mb-3">Try a different keyword or clear the filters to see the full queue again.</p>
<a class="btn btn-dark btn-sm" href="/dashboard.php">Clear filters</a>
<?php else: ?>
<h3 class="h5 mb-2">No requests yet</h3>
<p class="text-secondary mb-3">Submit the first project request from the home page to populate the queue.</p>
<a class="btn btn-dark btn-sm" href="/#request-form">Create the first request</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table align-middle mb-0 admin-table">
<thead>
<tr>
<th scope="col">Reference</th>
<th scope="col">Contact</th>
<th scope="col">Type</th>
<th scope="col">Timeline</th>
<th scope="col">Status</th>
<th scope="col">Submitted</th>
<th scope="col" class="text-end">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($requests as $request): ?>
<tr>
<td class="fw-semibold"><?= e(request_reference((int) $request['id'])) ?></td>
<td>
<div><?= e($request['name']) ?></div>
<div class="small text-secondary"><?= e($request['email']) ?></div>
<?php if (!empty($request['company'])): ?>
<div class="small text-secondary"><?= e((string) $request['company']) ?></div>
<?php endif; ?>
</td>
<td><?= e($request['project_type']) ?></td>
<td><?= e(timeline_options()[$request['timeline']] ?? $request['timeline']) ?></td>
<td><span class="badge <?= e(status_badge_class($request['status'])) ?>"><?= e(request_status_options()[$request['status']] ?? ucfirst((string) $request['status'])) ?></span></td>
<td class="small text-secondary"><?= e(format_datetime((string) $request['created_at'])) ?></td>
<td class="text-end"><a class="btn btn-sm btn-outline-secondary" href="/submission.php?id=<?= e((string) $request['id']) ?>">Open</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if ($totalPages > 1): ?>
<?php
$windowStart = max(1, $page - 2);
$windowEnd = min($totalPages, $windowStart + 4);
$windowStart = max(1, $windowEnd - 4);
?>
<nav class="border-top px-3 py-3" aria-label="Request pagination">
<ul class="pagination pagination-sm flex-wrap justify-content-center justify-content-md-end gap-1 mb-0">
<li class="page-item <?= $page <= 1 ? 'disabled' : '' ?>">
<a class="page-link" href="<?= e($buildDashboardUrl(['page' => max(1, $page - 1)])) ?>" <?= $page <= 1 ? 'tabindex="-1" aria-disabled="true"' : '' ?>>Previous</a>
</li>
<?php for ($pageNumber = $windowStart; $pageNumber <= $windowEnd; $pageNumber++): ?>
<li class="page-item <?= $pageNumber === $page ? 'active' : '' ?>">
<a class="page-link" href="<?= e($buildDashboardUrl(['page' => $pageNumber])) ?>"><?= e((string) $pageNumber) ?></a>
</li>
<?php endfor; ?>
<li class="page-item <?= $page >= $totalPages ? 'disabled' : '' ?>">
<a class="page-link" href="<?= e($buildDashboardUrl(['page' => min($totalPages, $page + 1)])) ?>" <?= $page >= $totalPages ? 'tabindex="-1" aria-disabled="true"' : '' ?>>Next</a>
</li>
</ul>
</nav>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</section>
</main>
<?php render_footer('dashboard'); ?>