40040-vm/request.php
Flatlogic Bot a646478110 MOSHA V4
2026-05-19 10:06:37 +00:00

269 lines
14 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/app.php';
ensure_request_table();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string) ($_POST['action'] ?? '') === 'logout') {
if (!verify_csrf($_POST['csrf_token'] ?? null)) {
flash('danger', 'Your session expired. Please try again.');
} else {
logout_current_user();
flash('primary', 'Signed out successfully.');
}
redirect('index.php');
}
$currentUser = require_auth();
$flash = consume_flash();
$requestId = isset($_GET['id']) ? (int) $_GET['id'] : 0;
$request = $requestId > 0 ? fetch_request_by_id($requestId) : null;
$decisionError = null;
if (!$request) {
http_response_code(404);
render_head('Request not found', 'The requested workflow record could not be located.');
?>
<body>
<?php render_nav($currentUser, 'dashboard'); ?>
<main class="app-shell">
<section class="container py-5">
<div class="shell-card narrow-panel">
<div class="section-kicker">Not found</div>
<h1 class="section-title">Request record not found.</h1>
<p class="section-copy">The request may have been removed or the link is incomplete.</p>
<a href="index.php#requests" class="btn btn-primary">Back to request list</a>
</div>
</section>
</main>
<?php render_footer(); ?>
<?php render_scripts(); ?>
<?php
exit;
}
if (!can_view_request($currentUser, $request)) {
http_response_code(403);
render_head('Access denied', 'You do not have permission to view this request.');
?>
<body>
<?php render_nav($currentUser, 'dashboard'); ?>
<main class="app-shell">
<section class="container py-5">
<div class="shell-card narrow-panel">
<div class="section-kicker">Access denied</div>
<h1 class="section-title">You cannot open this request.</h1>
<p class="section-copy">Employees only see their own requests, while approvers only see requests inside their department.</p>
<a href="index.php#requests" class="btn btn-primary">Back to request list</a>
</div>
</section>
</main>
<?php render_footer(); ?>
<?php render_scripts(); ?>
<?php
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verify_csrf($_POST['csrf_token'] ?? null)) {
flash('danger', 'Your session expired. Please refresh and try again.');
redirect('request.php?id=' . $requestId);
}
$action = trim((string) ($_POST['action'] ?? ''));
if ($action === 'approve_request') {
$result = apply_request_decision($request, $currentUser, (string) ($_POST['decision'] ?? ''), (string) ($_POST['comment'] ?? ''));
if (!empty($result['success'])) {
flash('success', $result['message']);
redirect('request.php?id=' . $requestId);
}
$decisionError = $result['message'] ?? 'Unable to update this request.';
$request = fetch_request_by_id($requestId);
}
}
$request = fetch_request_by_id($requestId);
$canApprove = can_approve_request($currentUser, $request);
$auditTrail = array_reverse(decode_audit((string) $request['audit_trail']));
$workflow = workflow_levels();
render_head(
$request['request_code'] . ' · ' . $request['title'],
'Request detail with status tracking, approval controls, and audit trail for departmental workflow.'
);
?>
<body>
<?php render_nav($currentUser, 'dashboard'); ?>
<?php render_flash_toast($flash); ?>
<main class="app-shell">
<section class="container py-4">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-4">
<div>
<a href="index.php#requests" class="text-decoration-none small text-muted">← Back to request list</a>
<h1 class="detail-title mt-2 mb-2"><?= e($request['title']) ?></h1>
<div class="d-flex flex-wrap gap-2 align-items-center">
<span class="badge text-bg-light border"><?= e($request['request_code']) ?></span>
<span class="badge <?= e(status_badge_class($request['status'])) ?>"><?= e($request['status']) ?></span>
<span class="badge <?= e(priority_badge_class($request['priority'])) ?>"><?= e($request['priority']) ?></span>
</div>
</div>
<div class="text-md-end detail-summary-text">
<div class="text-muted small">Requester</div>
<div class="fw-semibold"><?= e($request['requester_name']) ?></div>
<div class="text-muted small"><?= e($request['department']) ?> · <?= e($request['request_type']) ?></div>
</div>
</div>
<div class="shell-card mb-4">
<div class="section-kicker">Approval progress</div>
<div class="progress-route">
<?php foreach ($workflow as $level => $definition): ?>
<?php
$state = 'upcoming';
if ($request['status'] === 'Rejected' && (int) $request['current_stage'] === (int) $level) {
$state = 'rejected';
} elseif ($request['status'] === 'Approved' || (int) $request['current_stage'] > (int) $level) {
$state = 'done';
} elseif ((int) $request['current_stage'] === (int) $level && str_starts_with((string) $request['status'], 'Pending')) {
$state = 'current';
}
?>
<div class="progress-step <?= e('state-' . $state) ?>">
<span class="progress-index"><?= (int) $level ?></span>
<div>
<div class="fw-semibold"><?= e($definition['label']) ?></div>
<div class="text-muted small"><?= $level === 3 ? 'Global approval desk' : e($request['department']) . ' approver' ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="row g-4 align-items-start">
<div class="col-xl-7">
<div class="shell-card mb-4">
<div class="section-kicker">Request detail</div>
<div class="detail-grid">
<div>
<span class="detail-label">Request type</span>
<div class="detail-value"><?= e($request['request_type']) ?></div>
</div>
<div>
<span class="detail-label">Department</span>
<div class="detail-value"><?= e($request['department']) ?></div>
</div>
<div>
<span class="detail-label">Amount</span>
<div class="detail-value"><?= e(format_money($request['amount'])) ?></div>
</div>
<div>
<span class="detail-label">Needed by</span>
<div class="detail-value"><?= e(format_date($request['needed_by'])) ?></div>
</div>
<div>
<span class="detail-label">Created</span>
<div class="detail-value"><?= e(format_datetime($request['created_at'])) ?></div>
</div>
<div>
<span class="detail-label">Last updated</span>
<div class="detail-value"><?= e(format_datetime($request['updated_at'])) ?></div>
</div>
</div>
<hr class="my-4">
<span class="detail-label">Business justification</span>
<p class="detail-paragraph mb-0"><?= nl2br(e($request['justification'])) ?></p>
</div>
<div class="shell-card">
<div class="section-kicker">Audit trail</div>
<h2 class="section-title mb-3">Every action on this request</h2>
<div class="timeline-list">
<?php foreach ($auditTrail as $entry): ?>
<div class="timeline-item">
<div class="timeline-marker"></div>
<div>
<div class="d-flex flex-column flex-md-row justify-content-md-between gap-1">
<div class="fw-semibold"><?= e($entry['action'] ?? 'Update') ?> · <?= e($entry['actor'] ?? 'System') ?></div>
<div class="text-muted small"><?= e(format_datetime($entry['timestamp'] ?? '')) ?></div>
</div>
<div class="text-muted small mb-2"><?= e($entry['role'] ?? 'Workflow') ?></div>
<?php if (!empty($entry['comment'])): ?>
<p class="mb-0 detail-paragraph"><?= nl2br(e($entry['comment'])) ?></p>
<?php else: ?>
<p class="mb-0 text-muted small">No additional comment recorded.</p>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="col-xl-5">
<div class="shell-card mb-4">
<div class="section-kicker">Current status</div>
<h2 class="section-title mb-3">Workflow snapshot</h2>
<div class="surface-panel compact-panel">
<div class="d-flex justify-content-between align-items-start gap-3 mb-3">
<div>
<div class="surface-panel-title">Next expected step</div>
<div class="fw-semibold"><?= str_starts_with((string) $request['status'], 'Pending') ? e(level_label((int) $request['current_stage'])) : e($request['status']) ?></div>
</div>
<span class="badge <?= e(status_badge_class($request['status'])) ?>"><?= e($request['status']) ?></span>
</div>
<div class="small text-muted">Latest note</div>
<p class="mb-0 detail-paragraph"><?= e((string) ($request['last_comment'] ?: 'No comment recorded yet.')) ?></p>
</div>
</div>
<div class="shell-card mb-4">
<div class="section-kicker">Access rules</div>
<h2 class="section-title mb-3">Who can act next</h2>
<ul class="rule-list mb-0">
<li>Employees can submit and monitor their own requests.</li>
<li>Supervisors and HODs approve only inside their department.</li>
<li>Admin / Finance sees every request and closes level 3 approvals.</li>
</ul>
</div>
<div class="shell-card">
<div class="section-kicker">Decision</div>
<h2 class="section-title mb-3">Approve or reject</h2>
<?php if (!$canApprove): ?>
<div class="empty-state">
<strong>No action required from you.</strong>
<p class="mb-0">This request is either already closed or currently assigned to another approval level.</p>
</div>
<?php else: ?>
<?php if ($decisionError): ?>
<div class="alert alert-danger" role="alert"><?= e($decisionError) ?></div>
<?php endif; ?>
<form method="post" class="vstack gap-3">
<input type="hidden" name="csrf_token" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="approve_request">
<div>
<label class="form-label" for="comment">Approval note</label>
<textarea class="form-control" id="comment" name="comment" rows="5" maxlength="600" data-char-count-target="#comment-count" placeholder="Add context for the next approver or explain a rejection."></textarea>
<div class="d-flex justify-content-between mt-2 small text-muted">
<span>Notes are optional for approval, but required for rejection.</span>
<span id="comment-count">0 characters</span>
</div>
</div>
<div class="d-flex flex-column flex-sm-row gap-2">
<button type="submit" name="decision" value="approve" class="btn btn-primary flex-fill">Approve and continue</button>
<button type="submit" name="decision" value="reject" class="btn btn-outline-danger flex-fill">Reject request</button>
</div>
</form>
<?php endif; ?>
</div>
</div>
</div>
</section>
</main>
<?php render_footer(); ?>
<?php render_scripts(); ?>