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()}
+
+
+
+
+
+
+
+ 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()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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)}
+
+
+
+ );
+}
|