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

458 lines
28 KiB
PHP
Raw 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';
ensure_request_table();
$flash = consume_flash();
$currentUser = current_user();
$loginError = null;
$formErrors = [];
$oldForm = [
'request_type' => 'Purchase',
'title' => '',
'amount' => '',
'priority' => 'Standard',
'needed_by' => '',
'justification' => '',
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = trim((string) ($_POST['action'] ?? ''));
if (!verify_csrf($_POST['csrf_token'] ?? null)) {
flash('danger', 'Your session expired. Please try again.');
redirect('index.php');
}
if ($action === 'login') {
$candidate = authenticate_demo_user((string) ($_POST['email'] ?? ''), (string) ($_POST['password'] ?? ''));
if ($candidate) {
set_current_user($candidate);
flash('success', 'Signed in as ' . $candidate['name'] . '.');
redirect('index.php');
}
$loginError = 'Use one of the demo accounts below and the default password Twende2026.';
} elseif ($action === 'logout') {
logout_current_user();
flash('primary', 'Signed out successfully.');
redirect('index.php');
} elseif ($action === 'create_request') {
$currentUser = require_auth();
if (!can_submit_requests($currentUser)) {
flash('warning', 'Admin / Finance accounts can review and approve requests, but submission is reserved for department staff in this first MVP slice.');
redirect('index.php#submit');
}
$result = create_request($currentUser, $_POST);
if (!empty($result['success'])) {
flash('success', 'Request ' . $result['request_code'] . ' submitted and routed to the supervisor queue.');
redirect('request.php?id=' . (int) $result['id']);
}
$formErrors = $result['errors'] ?? [];
$oldForm = array_merge($oldForm, $result['values'] ?? []);
}
}
$currentUser = current_user();
$metrics = $currentUser ? dashboard_metrics($currentUser) : null;
$visibleRequests = $currentUser ? fetch_visible_requests($currentUser, 12) : [];
$queueRequests = $currentUser ? fetch_queue_requests($currentUser, 6) : [];
$timelineSample = [
['step' => 'Level 1', 'label' => 'Supervisor review'],
['step' => 'Level 2', 'label' => 'Head of Department'],
['step' => 'Level 3', 'label' => 'Admin / Finance'],
];
render_head(
'Department Request & Approval Manager',
'Submit departmental requests, route them through supervisor and HOD review, and close them with admin/finance sign-off.'
);
?>
<body>
<?php render_nav($currentUser, 'dashboard'); ?>
<?php render_flash_toast($flash); ?>
<?php if (!$currentUser): ?>
<main class="public-shell">
<section class="container py-4 py-lg-5">
<div class="row g-4 align-items-stretch">
<div class="col-lg-7">
<div class="shell-card hero-panel h-100">
<div class="eyebrow">Internal workflow portal</div>
<h1 class="hero-title">Track departmental requests from submission to final decision.</h1>
<p class="hero-copy">This first MVP slice already covers the end-to-end flow: employees submit a request, supervisors and HODs review by department level, and Admin / Finance completes the final sign-off with a visible audit trail.</p>
<div class="route-band mt-4">
<?php foreach ($timelineSample as $sample): ?>
<div class="route-chip">
<span class="route-step"><?= e($sample['step']) ?></span>
<span class="route-label"><?= e($sample['label']) ?></span>
</div>
<?php endforeach; ?>
</div>
<div class="row g-3 mt-4">
<div class="col-sm-4">
<div class="mini-stat">
<span class="mini-stat-label">Workflow</span>
<strong>Create → Approve → Track</strong>
</div>
</div>
<div class="col-sm-4">
<div class="mini-stat">
<span class="mini-stat-label">Departments</span>
<strong>Operations + HR demos</strong>
</div>
</div>
<div class="col-sm-4">
<div class="mini-stat">
<span class="mini-stat-label">Demo access</span>
<strong>Password: Twende2026</strong>
</div>
</div>
</div>
<div class="surface-panel mt-4">
<div class="surface-panel-title">What you can test immediately</div>
<ul class="feature-list mb-0">
<li>Submit a purchase, leave, access, travel, or maintenance request.</li>
<li>Switch roles using the demo accounts and approve at each department level.</li>
<li>Open a request detail page to review the full audit trail and latest note.</li>
</ul>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="shell-card auth-card h-100" id="login-panel">
<div class="section-kicker">Portal sign in</div>
<h2 class="section-title">Open the workflow dashboard</h2>
<p class="section-copy">Use a demo staff account to experience the employee, approver, and admin states.</p>
<?php if ($loginError): ?>
<div class="alert alert-danger small" role="alert"><?= e($loginError) ?></div>
<?php endif; ?>
<form method="post" class="vstack gap-3 mt-3">
<input type="hidden" name="csrf_token" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="login">
<div>
<label class="form-label" for="email">Email</label>
<input class="form-control" id="email" name="email" type="email" placeholder="mary.employee@company.local" required>
</div>
<div>
<div class="d-flex justify-content-between align-items-center">
<label class="form-label" for="password">Password</label>
<button type="button" class="btn btn-link btn-sm px-0" data-copy-password="Twende2026">Copy demo password</button>
</div>
<input class="form-control" id="password" name="password" type="password" placeholder="Twende2026" required>
</div>
<button class="btn btn-primary w-100" type="submit">Sign in</button>
</form>
<div class="table-responsive mt-4 demo-table-wrap">
<table class="table align-middle demo-table mb-0">
<thead>
<tr>
<th>User</th>
<th>Role</th>
<th>Department</th>
</tr>
</thead>
<tbody>
<?php foreach (demo_users() as $demoUser): ?>
<tr>
<td>
<div class="fw-semibold"><?= e($demoUser['name']) ?></div>
<div class="text-muted small"><?= e($demoUser['email']) ?></div>
</td>
<td><?= e($demoUser['role_label']) ?></td>
<td><?= e($demoUser['department']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
</main>
<?php else: ?>
<main class="app-shell">
<section class="container py-4" id="overview">
<div class="shell-card hero-panel dashboard-hero mb-4">
<div class="row g-4 align-items-start">
<div class="col-lg-8">
<div class="eyebrow">Signed in as <?= e($currentUser['role_label']) ?></div>
<h1 class="hero-title">Department request workflow, ready for daily use.</h1>
<p class="hero-copy">Submit requests, route them through the department chain, and keep every decision visible. The current policy is fixed to <strong>Supervisor → HOD → Admin / Finance</strong> for all departments in this first release.</p>
<div class="route-band mt-4">
<?php foreach ($timelineSample as $sample): ?>
<div class="route-chip">
<span class="route-step"><?= e($sample['step']) ?></span>
<span class="route-label"><?= e($sample['label']) ?></span>
</div>
<?php endforeach; ?>
</div>
<div class="hero-actions mt-4">
<?php if (can_submit_requests($currentUser)): ?>
<a href="#submit" class="btn btn-primary">Create request</a>
<?php endif; ?>
<a href="#queue" class="btn btn-outline-secondary">View approval queue</a>
<a href="#requests" class="btn btn-outline-secondary">Open request list</a>
</div>
</div>
<div class="col-lg-4">
<div class="surface-panel compact-panel">
<div class="surface-panel-title">Current profile</div>
<div class="profile-grid">
<div>
<span class="text-muted d-block small">Name</span>
<strong><?= e($currentUser['name']) ?></strong>
</div>
<div>
<span class="text-muted d-block small">Role</span>
<strong><?= e($currentUser['role_label']) ?></strong>
</div>
<div>
<span class="text-muted d-block small">Department</span>
<strong><?= e($currentUser['department']) ?></strong>
</div>
<div>
<span class="text-muted d-block small">Access</span>
<strong><?= ($currentUser['approval_level'] ?? 0) > 0 ? 'Review + approve' : 'Submit + track' ?></strong>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-sm-6 col-xl-3">
<div class="shell-card metric-card h-100">
<span class="metric-label"><?= ($currentUser['approval_level'] ?? 0) > 0 || is_admin_finance($currentUser) ? 'Visible requests' : 'My requests' ?></span>
<strong class="metric-value"><?= (int) ($metrics['visible_total'] ?? 0) ?></strong>
<span class="metric-subtext">Requests in your current scope</span>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="shell-card metric-card h-100">
<span class="metric-label">Open</span>
<strong class="metric-value"><?= (int) ($metrics['open'] ?? 0) ?></strong>
<span class="metric-subtext">Still moving through approval levels</span>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="shell-card metric-card h-100">
<span class="metric-label">Awaiting my action</span>
<strong class="metric-value"><?= (int) ($metrics['awaiting_action'] ?? 0) ?></strong>
<span class="metric-subtext">Items currently assigned to you</span>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="shell-card metric-card h-100">
<span class="metric-label">Approved</span>
<strong class="metric-value"><?= (int) ($metrics['approved'] ?? 0) ?></strong>
<span class="metric-subtext">Closed requests in your scope</span>
</div>
</div>
</div>
<div class="row g-4 align-items-start">
<div class="col-xl-7" id="submit">
<div class="shell-card h-100">
<div class="section-kicker">Create / input</div>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-2 mb-3">
<div>
<h2 class="section-title mb-1">Submit a new department request</h2>
<p class="section-copy mb-0">Capture the essentials once, then let the workflow route it automatically to each level.</p>
</div>
<?php if (!can_submit_requests($currentUser)): ?>
<span class="badge text-bg-light border">Submission disabled for Admin / Finance</span>
<?php endif; ?>
</div>
<?php if (!can_submit_requests($currentUser)): ?>
<div class="surface-panel">
<div class="surface-panel-title">Why submission is hidden</div>
<p class="mb-0 text-muted">This MVP keeps Admin / Finance focused on final approvals and oversight. Use an employee, supervisor, or HOD account to create a request and then switch back here to complete level 3.</p>
</div>
<?php else: ?>
<?php if ($formErrors): ?>
<div class="alert alert-danger" role="alert">
<div class="fw-semibold mb-2">Please fix the following before submitting:</div>
<ul class="mb-0 small ps-3">
<?php foreach ($formErrors as $error): ?>
<li><?= e($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form method="post" class="row g-3">
<input type="hidden" name="csrf_token" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="create_request">
<div class="col-md-6">
<label class="form-label" for="request_type">Request type</label>
<select class="form-select <?= isset($formErrors['request_type']) ? 'is-invalid' : '' ?>" id="request_type" name="request_type" required>
<?php foreach (request_type_options() as $type): ?>
<option value="<?= e($type) ?>" <?= $oldForm['request_type'] === $type ? 'selected' : '' ?>><?= e($type) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($formErrors['request_type'])): ?><div class="invalid-feedback"><?= e($formErrors['request_type']) ?></div><?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label" for="priority">Priority</label>
<select class="form-select <?= isset($formErrors['priority']) ? 'is-invalid' : '' ?>" id="priority" name="priority" required>
<?php foreach (priority_options() as $priority): ?>
<option value="<?= e($priority) ?>" <?= $oldForm['priority'] === $priority ? 'selected' : '' ?>><?= e($priority) ?></option>
<?php endforeach; ?>
</select>
<?php if (isset($formErrors['priority'])): ?><div class="invalid-feedback"><?= e($formErrors['priority']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label class="form-label" for="title">Short title</label>
<input class="form-control <?= isset($formErrors['title']) ? 'is-invalid' : '' ?>" id="title" name="title" type="text" maxlength="140" value="<?= e($oldForm['title']) ?>" placeholder="e.g. Laptop replacement for field coordinator" required>
<?php if (isset($formErrors['title'])): ?><div class="invalid-feedback"><?= e($formErrors['title']) ?></div><?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label" for="department_view">Department</label>
<input class="form-control" id="department_view" type="text" value="<?= e($currentUser['department']) ?>" readonly>
</div>
<div class="col-md-4">
<label class="form-label" for="amount">Amount (optional)</label>
<input class="form-control <?= isset($formErrors['amount']) ? 'is-invalid' : '' ?>" id="amount" name="amount" type="text" inputmode="decimal" value="<?= e($oldForm['amount']) ?>" placeholder="120000">
<?php if (isset($formErrors['amount'])): ?><div class="invalid-feedback"><?= e($formErrors['amount']) ?></div><?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label" for="needed_by">Needed by</label>
<input class="form-control <?= isset($formErrors['needed_by']) ? 'is-invalid' : '' ?>" id="needed_by" name="needed_by" type="date" value="<?= e($oldForm['needed_by']) ?>">
<?php if (isset($formErrors['needed_by'])): ?><div class="invalid-feedback"><?= e($formErrors['needed_by']) ?></div><?php endif; ?>
</div>
<div class="col-12">
<label class="form-label" for="justification">Business justification</label>
<textarea class="form-control <?= isset($formErrors['justification']) ? 'is-invalid' : '' ?>" id="justification" name="justification" rows="5" maxlength="1000" data-char-count-target="#justification-count" required><?= e($oldForm['justification']) ?></textarea>
<div class="d-flex justify-content-between mt-2 small text-muted">
<span>Explain the request clearly so each approver can act without extra follow-up.</span>
<span id="justification-count">0 characters</span>
</div>
<?php if (isset($formErrors['justification'])): ?><div class="invalid-feedback d-block"><?= e($formErrors['justification']) ?></div><?php endif; ?>
</div>
<div class="col-12 d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mt-2">
<div class="small text-muted">Submission starts at supervisor review and automatically advances through HOD and Admin / Finance.</div>
<button class="btn btn-primary" type="submit">Submit request</button>
</div>
</form>
<?php endif; ?>
</div>
</div>
<div class="col-xl-5" id="queue">
<div class="shell-card mb-4">
<div class="section-kicker">Approval queue</div>
<div class="d-flex justify-content-between align-items-start gap-2 mb-3">
<div>
<h2 class="section-title mb-1">Requests waiting on you</h2>
<p class="section-copy mb-0">Queue is filtered by your department and approval level.</p>
</div>
<span class="badge text-bg-light border"><?= (int) count($queueRequests) ?> active</span>
</div>
<?php if (!$queueRequests): ?>
<div class="empty-state">
<strong>No items in your queue.</strong>
<p class="mb-0">When a request reaches your level, it will appear here with a direct link to approve or reject.</p>
</div>
<?php else: ?>
<div class="queue-list">
<?php foreach ($queueRequests as $queued): ?>
<a class="queue-item" href="request.php?id=<?= (int) $queued['id'] ?>">
<div class="queue-item-top">
<span class="queue-code"><?= e($queued['request_code']) ?></span>
<span class="badge <?= e(status_badge_class($queued['status'])) ?>"><?= e($queued['status']) ?></span>
</div>
<div class="queue-title"><?= e($queued['title']) ?></div>
<div class="queue-meta"><?= e($queued['department']) ?> · <?= e($queued['request_type']) ?> · Requested by <?= e($queued['requester_name']) ?></div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="shell-card">
<div class="section-kicker">Routing policy</div>
<h2 class="section-title">Current approval design</h2>
<p class="section-copy">All departments share the same three-step ladder in this first version. Admin can see every request; department approvers only see requests in their own department.</p>
<div class="policy-grid">
<?php foreach (workflow_levels() as $level => $definition): ?>
<div class="policy-row">
<span class="policy-step">Level <?= (int) $level ?></span>
<div>
<div class="fw-semibold"><?= e($definition['label']) ?></div>
<div class="text-muted small"><?= $level === 3 ? 'Applies across all departments' : 'Applies inside the requesters department' ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<div class="shell-card mt-4" id="requests">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-3">
<div>
<div class="section-kicker">Request list</div>
<h2 class="section-title mb-1"><?= ($currentUser['approval_level'] ?? 0) > 0 || is_admin_finance($currentUser) ? 'Requests in view' : 'My request history' ?></h2>
<p class="section-copy mb-0">Open a row to inspect the approval trail, current stage, and latest decision comment.</p>
</div>
<span class="text-muted small">Showing <?= (int) count($visibleRequests) ?> most recent records</span>
</div>
<?php if (!$visibleRequests): ?>
<div class="empty-state">
<strong>No requests yet.</strong>
<p class="mb-0">Create your first request above and it will appear here immediately.</p>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table align-middle request-table mb-0">
<thead>
<tr>
<th>Request</th>
<th>Department</th>
<th>Requester</th>
<th>Status</th>
<th>Updated</th>
<th class="text-end">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($visibleRequests as $request): ?>
<tr>
<td>
<div class="fw-semibold"><?= e($request['title']) ?></div>
<div class="text-muted small"><?= e($request['request_code']) ?> · <?= e($request['request_type']) ?> · <?= format_money($request['amount']) ?></div>
</td>
<td><?= e($request['department']) ?></td>
<td>
<div><?= e($request['requester_name']) ?></div>
<div class="text-muted small"><?= e($request['requester_email']) ?></div>
</td>
<td>
<span class="badge <?= e(status_badge_class($request['status'])) ?>"><?= e($request['status']) ?></span>
<div class="text-muted small mt-1"><?= e(level_label((int) $request['current_stage'])) ?></div>
</td>
<td><?= e(format_datetime($request['updated_at'])) ?></td>
<td class="text-end">
<a class="btn btn-outline-secondary btn-sm" href="request.php?id=<?= (int) $request['id'] ?>">Open</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</section>
</main>
<?php endif; ?>
<?php render_footer(); ?>
<?php render_scripts(); ?>