nxtgauge-admin-solid/src/routes/admin/roles/index.tsx

151 lines
6.2 KiB
TypeScript
Raw Normal View History

import { A } from '@solidjs/router';
import { createResource, createSignal, Show, For } from 'solid-js';
import { Eye, Pencil, Trash2 } from 'lucide-solid';
import AdminShell from '~/components/AdminShell';
const API = '/api/gateway';
type Role = {
id: string;
name: string;
description?: string;
code?: string;
};
async function loadInternalRoles(): Promise<Role[]> {
try {
const res = await fetch(`${API}/api/admin/roles?audience=INTERNAL`);
if (!res.ok) throw new Error('Failed to load');
const data = await res.json();
const rows = Array.isArray(data) ? data : (data.roles || []);
return rows.map((r: any) => ({
id: r.id,
name: r.name,
description: r.description || '',
code: r.code || r.key || '',
}));
} catch {
return [];
}
}
export default function InternalRolesPage() {
const [roles, { refetch }] = createResource(loadInternalRoles);
const [deleting, setDeleting] = createSignal('');
const [deleteError, setDeleteError] = createSignal('');
const handleDelete = async (id: string, name: string) => {
if (!confirm(`Delete role "${name}"? This cannot be undone.`)) return;
setDeleting(id); setDeleteError('');
try {
const res = await fetch(`${API}/api/admin/roles/${id}`, { method: 'DELETE' });
if (!res.ok) throw new Error('Failed to delete role');
refetch();
} catch (err: any) {
setDeleteError(err.message || 'Failed to delete role');
} finally {
setDeleting('');
}
};
return (
<AdminShell>
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
{/* ── Page header ── */}
<div class="bg-white border-b border-gray-200 px-6 py-4">
<h1 class="text-xl font-semibold text-gray-900">Internal Role Management</h1>
<p class="text-sm text-gray-500 mt-0.5">Manage internal employee roles and permissions.</p>
</div>
{/* ── Tab bar ── */}
<div class="bg-white border-b border-gray-200 px-6 flex items-center justify-between sticky top-0 z-10">
<div class="flex gap-8">
<A href="/admin/roles"
class="py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium">
Roles
</A>
<A href="/admin/roles/create"
class="py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors">
Create Role
</A>
<A href="/admin/roles/templates"
class="py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors">
View Roles
</A>
</div>
<A
href="/admin/roles/create"
class="flex items-center gap-2 rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-[#0f2a4e] shadow-sm"
>
Create Internal Role
</A>
</div>
{/* ── Content ── */}
<div class="p-6">
<Show when={deleteError()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div>
</Show>
<div class="table-card">
<div class="overflow-x-auto">
<table class="data-table w-full text-sm">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<Show when={roles.loading}>
<tr><td colspan="3" class="py-10 text-center text-sm text-slate-400">Loading internal roles</td></tr>
</Show>
<Show when={!roles.loading && roles.error}>
<tr><td colspan="3" class="py-10 text-center text-sm text-red-500">Failed to load roles. Is the backend running?</td></tr>
</Show>
<Show when={!roles.loading && !roles.error && (roles()?.length ?? 0) === 0}>
<tr><td colspan="3" class="py-10 text-center text-sm text-slate-400">No internal roles found. Create your first role.</td></tr>
</Show>
<For each={roles()}>
{(role) => (
<tr class="hover:bg-slate-50">
<td>
<p class="font-medium text-gray-900">{role.name}</p>
<p class="mt-0.5 text-xs text-slate-500">{role.code || role.id?.slice(0, 8) || '—'}</p>
</td>
<td class="text-slate-600">{role.description || 'No description added yet.'}</td>
<td>
<div class="flex items-center justify-end gap-2">
<A href={`/admin/roles/${role.id}`} title="View Role"
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors">
<Eye size={14} class="text-gray-600" />
</A>
<A href={`/admin/roles/${role.id}/edit`} title="Edit Role"
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors">
<Pencil size={14} class="text-gray-600" />
</A>
<button
title="Delete Role"
disabled={deleting() === role.id}
onClick={() => handleDelete(role.id, role.name)}
class="action-btn flex items-center justify-center border-red-100 bg-red-50 hover:bg-red-100 transition-colors"
>
<Trash2 size={14} class="text-red-600" />
</button>
</div>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</div>
</div>
</div>
</AdminShell>
);
}