nxtgauge-admin-solid/src/routes/admin/approval/[id].tsx

123 lines
4.9 KiB
TypeScript
Raw Normal View History

import { A, useParams } from '@solidjs/router';
import { createMemo, createResource, createSignal, Show } from 'solid-js';
import AdminShell from '~/components/AdminShell';
const API = '/api/gateway';
type ApprovalDetail = {
id: string;
requestType?: string;
type?: string;
requestStatus?: string;
status?: string;
priority?: number;
createdAt?: string;
created_at?: string;
requester?: { name?: string; email?: string };
requesterName?: string;
requesterEmail?: string;
requester_name?: string;
requester_email?: string;
payload?: unknown;
};
async function loadApproval(id: string): Promise<ApprovalDetail | null> {
try {
const res = await fetch(`${API}/api/admin/approvals/${id}`);
if (!res.ok) return null;
return res.json();
} catch {
return null;
}
}
export default function ApprovalDetailPage() {
const params = useParams();
const [approval, { refetch }] = createResource(() => params.id, loadApproval);
const [acting, setActing] = createSignal('');
const [error, setError] = createSignal('');
const status = createMemo(() => (approval()?.requestStatus || approval()?.status || 'PENDING').toUpperCase());
const requestType = createMemo(() => (approval()?.requestType || approval()?.type || 'OTHER').toUpperCase());
const requesterName = createMemo(() => approval()?.requester?.name || approval()?.requesterName || approval()?.requester_name || 'Unknown');
const requesterEmail = createMemo(() => approval()?.requester?.email || approval()?.requesterEmail || approval()?.requester_email || '—');
const submittedAt = createMemo(() => approval()?.createdAt || approval()?.created_at || '');
const act = async (nextStatus: 'APPROVED' | 'REJECTED' | 'CHANGES_REQUESTED') => {
try {
setActing(nextStatus);
setError('');
const res = await fetch(`${API}/api/admin/approvals/${params.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: nextStatus }),
});
if (!res.ok) throw new Error(`Failed to mark as ${nextStatus.toLowerCase()}`);
refetch();
} catch (err: any) {
setError(err.message || 'Failed to update approval status');
} finally {
setActing('');
}
};
return (
<AdminShell>
<div class="page-hero-card page-actions">
<div>
<h1 class="page-title">Approval Detail</h1>
<p class="page-subtitle">Review one approval request in detail and take action.</p>
</div>
<A class="btn" href="/admin/approval">Back to Approval List</A>
</div>
<Show when={error()}>
<div class="error-box">{error()}</div>
</Show>
<Show when={approval.loading}>
<div class="card"><p class="notice">Loading approval...</p></div>
</Show>
<Show when={!approval.loading && !approval()}>
<div class="card"><p class="notice">Approval request not found.</p></div>
</Show>
<Show when={approval()}>
<div class="grid" style="margin-top:0">
<section class="card">
<h2 style="margin-bottom:8px">Request Summary</h2>
<p class="notice" style="margin:0"><strong>ID:</strong> {approval()!.id}</p>
<p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {requestType()}</p>
<p class="notice" style="margin:8px 0 0"><strong>Status:</strong> {status()}</p>
<p class="notice" style="margin:8px 0 0"><strong>Priority:</strong> {approval()!.priority ?? '—'}</p>
<p class="notice" style="margin:8px 0 0"><strong>Submitted:</strong> {submittedAt() ? new Date(submittedAt()).toLocaleString() : '—'}</p>
</section>
<section class="card">
<h2 style="margin-bottom:8px">Requester</h2>
<p class="notice" style="margin:0"><strong>Name:</strong> {requesterName()}</p>
<p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {requesterEmail()}</p>
<div class="actions">
<button class="btn primary" type="button" disabled={!!acting()} onClick={() => act('APPROVED')}>
{acting() === 'APPROVED' ? 'Approving...' : 'Approve'}
</button>
<button class="btn" type="button" disabled={!!acting()} onClick={() => act('CHANGES_REQUESTED')}>
{acting() === 'CHANGES_REQUESTED' ? 'Updating...' : 'Request Changes'}
</button>
<button class="btn danger" type="button" disabled={!!acting()} onClick={() => act('REJECTED')}>
{acting() === 'REJECTED' ? 'Rejecting...' : 'Reject'}
</button>
</div>
</section>
</div>
<section class="card" style="margin-top:16px">
<h2 style="margin-bottom:10px">Raw Payload</h2>
<pre class="json">{JSON.stringify(approval(), null, 2)}</pre>
</section>
</Show>
</AdminShell>
);
}