From 7e65337c60eb967cc4923f8725e6d31c50bcb249 Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Date: Thu, 19 Mar 2026 14:03:15 +0100 Subject: [PATCH] feat(admin): add missing approval and user detail/edit routes --- src/routes/admin/approval.tsx | 2 + src/routes/admin/approval/[id].tsx | 122 ++++++++++++++++ src/routes/admin/users.tsx | 3 +- src/routes/admin/users/[id]/edit.tsx | 182 ++++++++++++++++++++++++ src/routes/admin/users/details/[id].tsx | 89 ++++++++++++ 5 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 src/routes/admin/approval/[id].tsx create mode 100644 src/routes/admin/users/[id]/edit.tsx create mode 100644 src/routes/admin/users/details/[id].tsx diff --git a/src/routes/admin/approval.tsx b/src/routes/admin/approval.tsx index 9dcacd6..4b88a9b 100644 --- a/src/routes/admin/approval.tsx +++ b/src/routes/admin/approval.tsx @@ -1,3 +1,4 @@ +import { A } from '@solidjs/router'; import { createMemo, createResource, createSignal, For, Show } from 'solid-js'; import AdminShell from '~/components/AdminShell'; @@ -341,6 +342,7 @@ export default function ApprovalPage() { {submittedAt ? new Date(submittedAt).toLocaleString() : '—'}
+ diff --git a/src/routes/admin/approval/[id].tsx b/src/routes/admin/approval/[id].tsx new file mode 100644 index 0000000..54e6d5d --- /dev/null +++ b/src/routes/admin/approval/[id].tsx @@ -0,0 +1,122 @@ +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 { + 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 ( + +
+
+

Approval Detail

+

Review one approval request in detail and take action.

+
+ Back to Approval List +
+ + +
{error()}
+
+ + +

Loading approval...

+
+ + +

Approval request not found.

+
+ + +
+
+

Request Summary

+

ID: {approval()!.id}

+

Type: {requestType()}

+

Status: {status()}

+

Priority: {approval()!.priority ?? '—'}

+

Submitted: {submittedAt() ? new Date(submittedAt()).toLocaleString() : '—'}

+
+ +
+

Requester

+

Name: {requesterName()}

+

Email: {requesterEmail()}

+
+ + + +
+
+
+ +
+

Raw Payload

+
{JSON.stringify(approval(), null, 2)}
+
+
+
+ ); +} diff --git a/src/routes/admin/users.tsx b/src/routes/admin/users.tsx index c65ddc0..bfbb687 100644 --- a/src/routes/admin/users.tsx +++ b/src/routes/admin/users.tsx @@ -222,7 +222,8 @@ export default function UsersPage() {
- + +
diff --git a/src/routes/admin/users/[id]/edit.tsx b/src/routes/admin/users/[id]/edit.tsx new file mode 100644 index 0000000..c4939c3 --- /dev/null +++ b/src/routes/admin/users/[id]/edit.tsx @@ -0,0 +1,182 @@ +import { A, useNavigate, useParams } from '@solidjs/router'; +import { createMemo, createResource, createSignal, Show } from 'solid-js'; +import AdminShell from '~/components/AdminShell'; + +const API = '/api/gateway'; + +type Role = { + id: string; + name: string; +}; + +type User = { + id: string; + name?: string; + full_name?: string; + email: string; + roleId?: string; + role_id?: string; + role?: Role; + status?: 'ACTIVE' | 'INACTIVE' | 'PENDING'; + createdAt?: string; + created_at?: string; +}; + +async function fetchRoles(): Promise { + try { + const res = await fetch(`${API}/api/admin/roles?audience=INTERNAL`); + if (!res.ok) return []; + const data = await res.json(); + const rows = Array.isArray(data) ? data : (data.roles || []); + return rows.map((r: any) => ({ id: r.id, name: r.name })); + } catch { + return []; + } +} + +async function fetchUser(id: string): Promise { + try { + const adminRes = await fetch(`${API}/api/admin/users/${id}`); + if (adminRes.ok) return adminRes.json(); + + const fallback = await fetch(`${API}/api/users/${id}`); + if (!fallback.ok) return null; + return fallback.json(); + } catch { + return null; + } +} + +export default function EditUserPage() { + const navigate = useNavigate(); + const params = useParams(); + const [user] = createResource(() => params.id, fetchUser); + const [roles] = createResource(fetchRoles); + + const [name, setName] = createSignal(''); + const [email, setEmail] = createSignal(''); + const [roleId, setRoleId] = createSignal(''); + const [status, setStatus] = createSignal<'ACTIVE' | 'INACTIVE' | 'PENDING'>('ACTIVE'); + const [submitting, setSubmitting] = createSignal(false); + const [error, setError] = createSignal(''); + + createMemo(() => { + const u = user(); + if (!u) return null; + setName(u.name || u.full_name || ''); + setEmail(u.email || ''); + setRoleId(u.roleId || u.role_id || u.role?.id || ''); + setStatus((u.status || 'ACTIVE').toUpperCase() as 'ACTIVE' | 'INACTIVE' | 'PENDING'); + return null; + }); + + const save = async () => { + if (!name().trim() || !email().trim() || !roleId()) { + setError('Please fill in name, email, and role.'); + return; + } + + try { + setSubmitting(true); + setError(''); + const body = { + name: name().trim(), + email: email().trim(), + roleId: roleId(), + status: status().toLowerCase(), + }; + + let res = await fetch(`${API}/api/admin/users/${params.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + res = await fetch(`${API}/api/users/${params.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + } + + if (!res.ok) { + const payload = await res.json().catch(() => ({})); + throw new Error(payload.message || 'Failed to update user'); + } + navigate('/admin/users'); + } catch (err: any) { + setError(err.message || 'Failed to update user'); + } finally { + setSubmitting(false); + } + }; + + return ( + +
+
+

Edit User

+

Update user profile, role assignment, and account status.

+
+ +
+ + +
{error()}
+
+ + +

Loading user...

+
+ + +

User not found.

+
+ + +
+
+
+ + setName(e.currentTarget.value)} /> +
+
+ + setEmail(e.currentTarget.value)} /> +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/src/routes/admin/users/details/[id].tsx b/src/routes/admin/users/details/[id].tsx new file mode 100644 index 0000000..ec02899 --- /dev/null +++ b/src/routes/admin/users/details/[id].tsx @@ -0,0 +1,89 @@ +import { A, useParams } from '@solidjs/router'; +import { createMemo, createResource, Show } from 'solid-js'; +import AdminShell from '~/components/AdminShell'; + +const API = '/api/gateway'; + +type User = { + id: string; + name?: string; + full_name?: string; + email: string; + role?: string; + role_name?: string; + roleId?: string; + status?: 'ACTIVE' | 'INACTIVE' | 'PENDING'; + createdAt?: string; + created_at?: string; + updatedAt?: string; + updated_at?: string; +}; + +async function fetchUser(id: string): Promise { + try { + const adminRes = await fetch(`${API}/api/admin/users/${id}`); + if (adminRes.ok) return adminRes.json(); + + const fallback = await fetch(`${API}/api/users/${id}`); + if (!fallback.ok) return null; + return fallback.json(); + } catch { + return null; + } +} + +export default function UserDetailPage() { + const params = useParams(); + const [user] = createResource(() => params.id, fetchUser); + + const displayName = createMemo(() => user()?.name || user()?.full_name || 'Unknown User'); + const roleName = createMemo(() => user()?.role_name || user()?.role || 'UNKNOWN'); + const createdAt = createMemo(() => user()?.createdAt || user()?.created_at || ''); + const updatedAt = createMemo(() => user()?.updatedAt || user()?.updated_at || ''); + + return ( + +
+
+

User Detail

+

Review account profile, role assignment, and account status.

+
+ +
+ + +

Loading user...

+
+ + +

User not found.

+
+ + +
+
+

Profile

+

ID: {user()!.id}

+

Name: {displayName()}

+

Email: {user()!.email}

+
+
+

Account

+

Role: {roleName()}

+

Status: {user()!.status || 'PENDING'}

+

Created: {createdAt() ? new Date(createdAt()).toLocaleString() : '—'}

+

Updated: {updatedAt() ? new Date(updatedAt()).toLocaleString() : '—'}

+
+
+ +
+

Raw Data

+
{JSON.stringify(user(), null, 2)}
+
+
+
+ ); +}