feat(admin): add route aliases and missing detail pages for parity
This commit is contained in:
parent
16be71a4ce
commit
97f9317797
32 changed files with 893 additions and 3 deletions
|
|
@ -12,6 +12,7 @@ const TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = [
|
|||
tabs: [
|
||||
{ href: '/admin/roles', label: 'Internal Roles', exact: true },
|
||||
{ href: '/admin/roles/create', label: 'Create Role' },
|
||||
{ href: '/admin/roles/templates', label: 'Role Templates' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -50,6 +51,14 @@ const TAB_SETS: Array<{ prefixes: string[]; tabs: Tab[] }> = [
|
|||
];
|
||||
|
||||
const PAGE_TITLES: Array<{ prefix: string; title: string }> = [
|
||||
{ prefix: '/admin/workspace', title: 'Dashboard Workspace' },
|
||||
{ prefix: '/admin/settings', title: 'Settings' },
|
||||
{ prefix: '/admin/role-modules', title: 'Role Modules' },
|
||||
{ prefix: '/admin/modules', title: 'Module Management' },
|
||||
{ prefix: '/admin/responses', title: 'Lead Responses' },
|
||||
{ prefix: '/admin/applications', title: 'Applications' },
|
||||
{ prefix: '/admin/financial', title: 'Financial Management' },
|
||||
{ prefix: '/admin/help', title: 'Support Management' },
|
||||
{ prefix: '/admin/verification-status', title: 'Verification Status' },
|
||||
{ prefix: '/admin/verification', title: 'Verification Review' },
|
||||
{ prefix: '/admin/approval', title: 'Approval Management' },
|
||||
|
|
@ -69,7 +78,10 @@ const PAGE_TITLES: Array<{ prefix: string; title: string }> = [
|
|||
{ prefix: '/admin/ledger', title: 'Ledger Management' },
|
||||
{ prefix: '/admin/report', title: 'Report Management' },
|
||||
{ prefix: '/admin/roles', title: 'Internal Role Management' },
|
||||
{ prefix: '/admin/external-role-management', title: 'External Role Management' },
|
||||
{ prefix: '/admin/internal-role-management', title: 'Internal Role Management' },
|
||||
{ prefix: '/admin/runtime-roles', title: 'External Role Management' },
|
||||
{ prefix: '/admin/onboarding-management', title: 'Onboarding Management' },
|
||||
{ prefix: '/admin/onboarding-schemas', title: 'Onboarding Management' },
|
||||
{ prefix: '/admin', title: 'Dashboard' },
|
||||
];
|
||||
|
|
|
|||
9
src/routes/admin/applications.tsx
Normal file
9
src/routes/admin/applications.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function ApplicationsAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/jobs', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to jobs management...</p></div></AdminShell>;
|
||||
}
|
||||
|
|
@ -77,6 +77,7 @@ export default function CompanyPage() {
|
|||
<h1 class="page-title">Company Management</h1>
|
||||
<p class="page-subtitle">Manage all company accounts on the platform.</p>
|
||||
</div>
|
||||
<A class="btn navy" href="/admin/company/create">Create Company</A>
|
||||
</div>
|
||||
|
||||
<Show when={actionError()}>
|
||||
|
|
|
|||
101
src/routes/admin/company/[id].tsx
Normal file
101
src/routes/admin/company/[id].tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { A, useParams } from '@solidjs/router';
|
||||
import { createMemo, createResource, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
type CompanyDetail = {
|
||||
id: string;
|
||||
company_name?: string;
|
||||
companyName?: string;
|
||||
company_id?: string;
|
||||
companyId?: string;
|
||||
industry?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
website_url?: string;
|
||||
websiteUrl?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
address?: string;
|
||||
status?: string;
|
||||
is_verified?: boolean;
|
||||
isVerified?: boolean;
|
||||
description?: string;
|
||||
pan?: string;
|
||||
gst?: string;
|
||||
tan?: string;
|
||||
};
|
||||
|
||||
async function fetchCompany(id: string): Promise<CompanyDetail | null> {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/admin/companies/${id}`);
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data.company || data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function CompanyDetailPage() {
|
||||
const params = useParams();
|
||||
const [company] = createResource(() => params.id, fetchCompany);
|
||||
|
||||
const name = createMemo(() => company()?.company_name || company()?.companyName || 'Company');
|
||||
const cid = createMemo(() => company()?.company_id || company()?.companyId || company()?.id || '—');
|
||||
const website = createMemo(() => company()?.website_url || company()?.websiteUrl || '');
|
||||
const isVerified = createMemo(() => Boolean(company()?.is_verified ?? company()?.isVerified));
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Company Detail</h1>
|
||||
<p class="page-subtitle">Review company profile, contact details, and verification readiness.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/company">Back to Companies</A>
|
||||
<A class="btn navy" href="/admin/approval">Open Approval Management</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={company.loading}>
|
||||
<div class="card"><p class="notice">Loading company...</p></div>
|
||||
</Show>
|
||||
<Show when={!company.loading && !company()}>
|
||||
<div class="card"><p class="notice">Company not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={company()}>
|
||||
<div class="grid" style="margin-top:0">
|
||||
<section class="card">
|
||||
<h2 style="margin-bottom:8px">Company</h2>
|
||||
<p class="notice" style="margin:0"><strong>Name:</strong> {name()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Company ID:</strong> {cid()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Industry:</strong> {company()!.industry || '—'}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Status:</strong> {company()!.status || '—'}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Verification:</strong> {isVerified() ? 'Verified' : 'Not Verified'}</p>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2 style="margin-bottom:8px">Contact</h2>
|
||||
<p class="notice" style="margin:0"><strong>Email:</strong> {company()!.email || '—'}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Phone:</strong> {company()!.phone || '—'}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>City/State:</strong> {[company()!.city, company()!.state].filter(Boolean).join(', ') || '—'}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Address:</strong> {company()!.address || '—'}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Website:</strong> {website() ? <a href={website()} target="_blank" rel="noreferrer">{website()}</a> : '—'}</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card" style="margin-top:16px">
|
||||
<h2 style="margin-bottom:8px">Compliance</h2>
|
||||
<div class="field-grid-2">
|
||||
<p class="notice" style="margin:0"><strong>PAN:</strong> {company()!.pan || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>GST:</strong> {company()!.gst || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>TAN:</strong> {company()!.tan || '—'}</p>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
111
src/routes/admin/company/create.tsx
Normal file
111
src/routes/admin/company/create.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { A, useNavigate } from '@solidjs/router';
|
||||
import { createSignal, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
export default function CreateCompanyPage() {
|
||||
const navigate = useNavigate();
|
||||
const [saving, setSaving] = createSignal(false);
|
||||
const [error, setError] = createSignal('');
|
||||
const [form, setForm] = createSignal({
|
||||
companyName: '',
|
||||
companyId: '',
|
||||
address: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
industry: 'TECHNOLOGY',
|
||||
description: '',
|
||||
websiteUrl: '',
|
||||
});
|
||||
|
||||
const setField = (k: keyof ReturnType<typeof form>, v: string) => {
|
||||
setForm((prev) => ({ ...prev, [k]: v }));
|
||||
};
|
||||
|
||||
const submit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
const f = form();
|
||||
if (!f.companyName.trim() || !f.companyId.trim() || !f.address.trim() || !f.email.trim() || !f.phone.trim()) {
|
||||
setError('Please fill all required fields.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setSaving(true);
|
||||
setError('');
|
||||
const res = await fetch(`${API}/api/admin/companies`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(f),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const payload = await res.json().catch(() => ({}));
|
||||
throw new Error(payload.message || 'Failed to create company');
|
||||
}
|
||||
navigate('/admin/company');
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to create company');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Create Company</h1>
|
||||
<p class="page-subtitle">Add a new organization profile to the admin company catalog.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/company">Back to Companies</A>
|
||||
</div>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<form class="card" onSubmit={submit}>
|
||||
<div class="field-grid-2">
|
||||
<div class="field">
|
||||
<label>Company Name *</label>
|
||||
<input value={form().companyName} onInput={(e) => setField('companyName', e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Company ID *</label>
|
||||
<input value={form().companyId} onInput={(e) => setField('companyId', e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Industry</label>
|
||||
<input value={form().industry} onInput={(e) => setField('industry', e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Website</label>
|
||||
<input value={form().websiteUrl} onInput={(e) => setField('websiteUrl', e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Email *</label>
|
||||
<input type="email" value={form().email} onInput={(e) => setField('email', e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Phone *</label>
|
||||
<input value={form().phone} onInput={(e) => setField('phone', e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field" style="grid-column:1/-1">
|
||||
<label>Address *</label>
|
||||
<input value={form().address} onInput={(e) => setField('address', e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field" style="grid-column:1/-1">
|
||||
<label>Description</label>
|
||||
<textarea rows={3} value={form().description} onInput={(e) => setField('description', e.currentTarget.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<A class="btn" href="/admin/company">Cancel</A>
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Creating...' : 'Create Company'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { A, useNavigate } from '@solidjs/router';
|
||||
import { createResource, createSignal, Show, For } from 'solid-js';
|
||||
import { A, useNavigate, useSearchParams } from '@solidjs/router';
|
||||
import { createResource, createSignal, Show, For, onMount } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
|
@ -60,6 +60,7 @@ function isActive(e: Employee): boolean {
|
|||
|
||||
export default function EmployeesPage() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [employees, { refetch }] = createResource(loadEmployees);
|
||||
const [roles] = createResource(loadRoles);
|
||||
|
|
@ -67,6 +68,12 @@ export default function EmployeesPage() {
|
|||
// tabs: list | create
|
||||
const [view, setView] = createSignal<'list' | 'create'>('list');
|
||||
|
||||
onMount(() => {
|
||||
if (searchParams.tab === 'create') {
|
||||
setView('create');
|
||||
}
|
||||
});
|
||||
|
||||
// create form fields
|
||||
const [formName, setFormName] = createSignal('');
|
||||
const [formEmail, setFormEmail] = createSignal('');
|
||||
|
|
|
|||
132
src/routes/admin/employees/[id]/edit.tsx
Normal file
132
src/routes/admin/employees/[id]/edit.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
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 Employee = { id: string; name?: string; full_name?: string; email: string; role_id?: string; role?: { id?: string } };
|
||||
|
||||
async function fetchEmployee(id: string): Promise<Employee | null> {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/admin/employees/${id}`);
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRoles(): Promise<Role[]> {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/admin/roles?audience=INTERNAL`);
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return Array.isArray(data) ? data : (data.roles || []);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default function EditEmployeePage() {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [employee] = createResource(() => params.id, fetchEmployee);
|
||||
const [roles] = createResource(fetchRoles);
|
||||
|
||||
const [name, setName] = createSignal('');
|
||||
const [email, setEmail] = createSignal('');
|
||||
const [roleId, setRoleId] = createSignal('');
|
||||
const [saving, setSaving] = createSignal(false);
|
||||
const [error, setError] = createSignal('');
|
||||
|
||||
createMemo(() => {
|
||||
const e = employee();
|
||||
if (!e) return null;
|
||||
setName(e.name || e.full_name || '');
|
||||
setEmail(e.email || '');
|
||||
setRoleId(e.role_id || e.role?.id || '');
|
||||
return null;
|
||||
});
|
||||
|
||||
const submit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
if (!name().trim() || !email().trim() || !roleId()) {
|
||||
setError('Name, email, and role are required.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setSaving(true);
|
||||
setError('');
|
||||
const res = await fetch(`${API}/api/admin/employees/${params.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: name().trim(), email: email().trim(), role_id: roleId() }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const payload = await res.json().catch(() => ({}));
|
||||
throw new Error(payload.message || 'Failed to update employee');
|
||||
}
|
||||
navigate('/admin/employees');
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to update employee');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Edit Employee</h1>
|
||||
<p class="page-subtitle">Update internal employee profile and role assignment.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/employees">Back to Employees</A>
|
||||
</div>
|
||||
|
||||
<Show when={error()}>
|
||||
<div class="error-box">{error()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={employee.loading}>
|
||||
<div class="card"><p class="notice">Loading employee...</p></div>
|
||||
</Show>
|
||||
<Show when={!employee.loading && !employee()}>
|
||||
<div class="card"><p class="notice">Employee not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={employee()}>
|
||||
<form class="card" onSubmit={submit}>
|
||||
<div class="field-grid-2">
|
||||
<div class="field">
|
||||
<label>Full Name</label>
|
||||
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Email</label>
|
||||
<input type="email" value={email()} onInput={(e) => setEmail(e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Role</label>
|
||||
<select value={roleId()} onChange={(e) => setRoleId(e.currentTarget.value)}>
|
||||
<option value="">Select role...</option>
|
||||
<Show when={!roles.loading}>
|
||||
{roles()?.map((r) => (
|
||||
<option value={r.id}>{r.name}</option>
|
||||
))}
|
||||
</Show>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<A class="btn" href="/admin/employees">Cancel</A>
|
||||
<button class="btn navy" type="submit" disabled={saving()}>
|
||||
{saving() ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
19
src/routes/admin/employees/create.tsx
Normal file
19
src/routes/admin/employees/create.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function CreateEmployeeAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
onMount(() => {
|
||||
navigate('/admin/employees?tab=create', { replace: true });
|
||||
});
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card">
|
||||
<p class="notice">Redirecting to employee create form...</p>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
13
src/routes/admin/external-role-management.tsx
Normal file
13
src/routes/admin/external-role-management.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function ExternalRoleManagementAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/runtime-roles', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to external role management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
9
src/routes/admin/financial/adjust-credit.tsx
Normal file
9
src/routes/admin/financial/adjust-credit.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function AdjustCreditAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/credit', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to credit management...</p></div></AdminShell>;
|
||||
}
|
||||
9
src/routes/admin/financial/reconcile.tsx
Normal file
9
src/routes/admin/financial/reconcile.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function ReconcileAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/ledger', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to ledger management...</p></div></AdminShell>;
|
||||
}
|
||||
13
src/routes/admin/help.tsx
Normal file
13
src/routes/admin/help.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function HelpAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/support', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to support management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
25
src/routes/admin/help/[id].tsx
Normal file
25
src/routes/admin/help/[id].tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { A, useParams } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function HelpArticlePage() {
|
||||
const params = useParams();
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Help Article</h1>
|
||||
<p class="page-subtitle">Legacy help article route preserved for migration compatibility.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/support">Back to Support</A>
|
||||
</div>
|
||||
<section class="card">
|
||||
<p class="notice" style="margin:0">
|
||||
Article ID: <strong>{params.id}</strong>
|
||||
</p>
|
||||
<p class="notice" style="margin:8px 0 0">
|
||||
Detailed knowledge base article rendering is handled in the support/KB modules during this migration phase.
|
||||
</p>
|
||||
</section>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
20
src/routes/admin/help/support-bridge.tsx
Normal file
20
src/routes/admin/help/support-bridge.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { A } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function HelpSupportBridgePage() {
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="page-title">Support Bridge</h1>
|
||||
<p class="page-subtitle" style="margin-top:8px">
|
||||
This legacy help bridge now routes through the unified Support Management module.
|
||||
</p>
|
||||
</div>
|
||||
<section class="card">
|
||||
<div class="actions">
|
||||
<A class="btn navy" href="/admin/support">Open Support Management</A>
|
||||
</div>
|
||||
</section>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
13
src/routes/admin/internal-role-management.tsx
Normal file
13
src/routes/admin/internal-role-management.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function InternalRoleManagementAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/roles', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to internal role management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
9
src/routes/admin/kb/articles.tsx
Normal file
9
src/routes/admin/kb/articles.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function KbArticlesAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/kb', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to knowledge base...</p></div></AdminShell>;
|
||||
}
|
||||
17
src/routes/admin/kb/articles/[id].tsx
Normal file
17
src/routes/admin/kb/articles/[id].tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { A, useNavigate, useParams } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function KbArticleAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
onMount(() => navigate('/admin/kb', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card">
|
||||
<p class="notice">Redirecting article <strong>{params.id}</strong> to knowledge base module...</p>
|
||||
<div class="actions"><A class="btn" href="/admin/kb">Open Knowledge Base</A></div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
17
src/routes/admin/kb/articles/[id]/edit.tsx
Normal file
17
src/routes/admin/kb/articles/[id]/edit.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { A, useNavigate, useParams } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function KbArticleEditAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
onMount(() => navigate('/admin/kb', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card">
|
||||
<p class="notice">Redirecting article edit <strong>{params.id}</strong> to knowledge base module...</p>
|
||||
<div class="actions"><A class="btn" href="/admin/kb">Open Knowledge Base</A></div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
9
src/routes/admin/kb/articles/new.tsx
Normal file
9
src/routes/admin/kb/articles/new.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function KbArticleNewAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/kb', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to knowledge base editor...</p></div></AdminShell>;
|
||||
}
|
||||
9
src/routes/admin/kb/categories.tsx
Normal file
9
src/routes/admin/kb/categories.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function KbCategoriesAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/kb', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to knowledge base categories...</p></div></AdminShell>;
|
||||
}
|
||||
82
src/routes/admin/leads/[id].tsx
Normal file
82
src/routes/admin/leads/[id].tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { A, useParams } from '@solidjs/router';
|
||||
import { createMemo, createResource, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
type Lead = {
|
||||
id: string;
|
||||
requirementId?: string;
|
||||
requirement_id?: string;
|
||||
profession?: string;
|
||||
customerId?: string;
|
||||
customer_id?: string;
|
||||
professionalId?: string;
|
||||
professional_id?: string;
|
||||
status?: string;
|
||||
createdAt?: string;
|
||||
created_at?: string;
|
||||
updatedAt?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
async function fetchLead(id: string): Promise<Lead | null> {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/leads/${id}`);
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data.lead || data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function LeadDetailPage() {
|
||||
const params = useParams();
|
||||
const [lead] = createResource(() => params.id, fetchLead);
|
||||
|
||||
const requirementId = createMemo(() => lead()?.requirementId || lead()?.requirement_id || '');
|
||||
const customerId = createMemo(() => lead()?.customerId || lead()?.customer_id || '');
|
||||
const professionalId = createMemo(() => lead()?.professionalId || lead()?.professional_id || '');
|
||||
const createdAt = createMemo(() => lead()?.createdAt || lead()?.created_at || '');
|
||||
const updatedAt = createMemo(() => lead()?.updatedAt || lead()?.updated_at || '');
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Lead Detail</h1>
|
||||
<p class="page-subtitle">Review one lead and its linked requirement identifiers.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/leads">Back to Leads</A>
|
||||
</div>
|
||||
|
||||
<Show when={lead.loading}>
|
||||
<div class="card"><p class="notice">Loading lead...</p></div>
|
||||
</Show>
|
||||
<Show when={!lead.loading && !lead()}>
|
||||
<div class="card"><p class="notice">Lead not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={lead()}>
|
||||
<section class="card">
|
||||
<div class="field-grid-2">
|
||||
<p class="notice" style="margin:0"><strong>Lead ID:</strong> {lead()!.id}</p>
|
||||
<p class="notice" style="margin:0"><strong>Status:</strong> {lead()!.status || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Profession:</strong> {lead()!.profession || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Requirement ID:</strong> {requirementId() || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Customer ID:</strong> {customerId() || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Professional ID:</strong> {professionalId() || 'Unassigned'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Created:</strong> {createdAt() ? new Date(createdAt()).toLocaleString() : '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Updated:</strong> {updatedAt() ? new Date(updatedAt()).toLocaleString() : '—'}</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<Show when={requirementId()}>
|
||||
<A class="btn" href={`/admin/jobs/${requirementId()}`}>Open Linked Requirement/Job</A>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
9
src/routes/admin/modules.tsx
Normal file
9
src/routes/admin/modules.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function ModulesAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/internal-dashboard-management', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to dashboard module configuration...</p></div></AdminShell>;
|
||||
}
|
||||
13
src/routes/admin/onboarding-management.tsx
Normal file
13
src/routes/admin/onboarding-management.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function OnboardingManagementAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/onboarding-schemas', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card"><p class="notice">Redirecting to onboarding management...</p></div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ export default function PhotographerPage() {
|
|||
</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/users/${item.id}`}>View</A>
|
||||
<A class="btn" href={`/admin/photographer/${item.id}`}>View</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
77
src/routes/admin/photographer/[id].tsx
Normal file
77
src/routes/admin/photographer/[id].tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { A, useParams } from '@solidjs/router';
|
||||
import { createMemo, createResource, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
type Photographer = {
|
||||
id: string;
|
||||
name?: string;
|
||||
full_name?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
status?: string;
|
||||
created_at?: string;
|
||||
createdAt?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
experience?: string;
|
||||
portfolio_url?: string;
|
||||
};
|
||||
|
||||
async function fetchPhotographer(id: string): Promise<Photographer | null> {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/admin/users/${id}`);
|
||||
if (res.ok) return res.json();
|
||||
const fallback = await fetch(`${API}/api/users/${id}`);
|
||||
if (!fallback.ok) return null;
|
||||
return fallback.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function PhotographerDetailPage() {
|
||||
const params = useParams();
|
||||
const [profile] = createResource(() => params.id, fetchPhotographer);
|
||||
|
||||
const name = createMemo(() => profile()?.name || profile()?.full_name || 'Photographer');
|
||||
const created = createMemo(() => profile()?.createdAt || profile()?.created_at || '');
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Photographer Detail</h1>
|
||||
<p class="page-subtitle">View profile snapshot and account metadata for one photographer.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/photographer">Back to Photographer List</A>
|
||||
<A class="btn" href={`/admin/users/details/${params.id}`}>Open User Detail</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={profile.loading}>
|
||||
<div class="card"><p class="notice">Loading photographer...</p></div>
|
||||
</Show>
|
||||
<Show when={!profile.loading && !profile()}>
|
||||
<div class="card"><p class="notice">Photographer not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={profile()}>
|
||||
<section class="card">
|
||||
<div class="field-grid-2">
|
||||
<p class="notice" style="margin:0"><strong>Name:</strong> {name()}</p>
|
||||
<p class="notice" style="margin:0"><strong>Email:</strong> {profile()!.email || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Phone:</strong> {profile()!.phone || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Status:</strong> {profile()!.status || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>City/State:</strong> {[profile()!.city, profile()!.state].filter(Boolean).join(', ') || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Experience:</strong> {profile()!.experience || '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Created:</strong> {created() ? new Date(created()).toLocaleString() : '—'}</p>
|
||||
<p class="notice" style="margin:0"><strong>Portfolio:</strong> {profile()!.portfolio_url || '—'}</p>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
10
src/routes/admin/profile/[id].tsx
Normal file
10
src/routes/admin/profile/[id].tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate, useParams } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function ProfileAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
onMount(() => navigate(`/admin/users/details/${params.id}`, { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to user profile detail...</p></div></AdminShell>;
|
||||
}
|
||||
9
src/routes/admin/responses.tsx
Normal file
9
src/routes/admin/responses.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function ResponsesAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/leads', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to lead responses management...</p></div></AdminShell>;
|
||||
}
|
||||
9
src/routes/admin/role-modules.tsx
Normal file
9
src/routes/admin/role-modules.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function RoleModulesAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/role-ui-configs', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to role module configuration...</p></div></AdminShell>;
|
||||
}
|
||||
92
src/routes/admin/roles/templates.tsx
Normal file
92
src/routes/admin/roles/templates.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { A } from '@solidjs/router';
|
||||
import { createMemo, createResource, For, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
type RoleTemplate = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
code?: string;
|
||||
audience?: string;
|
||||
};
|
||||
|
||||
async function fetchTemplates(): Promise<RoleTemplate[]> {
|
||||
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,
|
||||
description: r.description || '',
|
||||
code: r.code || r.key || '',
|
||||
audience: r.audience || 'INTERNAL',
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default function RoleTemplatesPage() {
|
||||
const [templates] = createResource(fetchTemplates);
|
||||
const count = createMemo(() => (templates() || []).length);
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Role Templates</h1>
|
||||
<p class="page-subtitle">Starter role presets for faster internal role creation and cloning.</p>
|
||||
</div>
|
||||
<A class="btn navy" href="/admin/roles/create">Create Internal Role</A>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e2e8f0">
|
||||
<h2 style="margin:0;font-size:16px">Available Templates</h2>
|
||||
<span style="font-size:12px;color:#64748b">{count()} template{count() !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Code</th>
|
||||
<th class="align-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={templates.loading}>
|
||||
<tr><td colspan="4" style="text-align:center;padding:32px;color:#64748b">Loading templates...</td></tr>
|
||||
</Show>
|
||||
<Show when={!templates.loading && count() === 0}>
|
||||
<tr><td colspan="4" style="text-align:center;padding:32px;color:#94a3b8">No templates available yet.</td></tr>
|
||||
</Show>
|
||||
<Show when={!templates.loading && count() > 0}>
|
||||
<For each={templates()}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{item.name}</td>
|
||||
<td style="color:#475569">{item.description || '—'}</td>
|
||||
<td style="color:#64748b;font-family:ui-monospace,SFMono-Regular,Menlo,monospace">{item.code || '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="btn" href={`/admin/roles/${item.id}`}>View</A>
|
||||
<A class="btn" href="/admin/roles/create">Use As Base</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
9
src/routes/admin/settings.tsx
Normal file
9
src/routes/admin/settings.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function SettingsAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin/internal-dashboard-management', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to settings/configuration...</p></div></AdminShell>;
|
||||
}
|
||||
9
src/routes/admin/workspace.tsx
Normal file
9
src/routes/admin/workspace.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function WorkspaceAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
onMount(() => navigate('/admin', { replace: true }));
|
||||
return <AdminShell><div class="card"><p class="notice">Redirecting to dashboard workspace...</p></div></AdminShell>;
|
||||
}
|
||||
16
src/routes/admin/workspace/[menuId].tsx
Normal file
16
src/routes/admin/workspace/[menuId].tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { onMount } from 'solid-js';
|
||||
import { useNavigate, useParams } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function WorkspaceMenuAliasPage() {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
onMount(() => navigate('/admin', { replace: true }));
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="card">
|
||||
<p class="notice">Redirecting workspace menu <strong>{params.menuId}</strong> to dashboard...</p>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue