feat(admin): add jobs and verification parity routes
This commit is contained in:
parent
7e65337c60
commit
6c6667b8be
5 changed files with 407 additions and 0 deletions
118
src/routes/admin/jobs/[id].tsx
Normal file
118
src/routes/admin/jobs/[id].tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { A, useParams } from '@solidjs/router';
|
||||
import { createMemo, createResource, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
type Job = {
|
||||
id: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
requiredSkills?: string[];
|
||||
required_skills?: string[];
|
||||
experienceLevel?: string;
|
||||
experience_level?: string;
|
||||
status?: string;
|
||||
clientName?: string;
|
||||
client_name?: string;
|
||||
companyName?: string;
|
||||
company_name?: string;
|
||||
location?: string;
|
||||
hourlyRateMin?: number;
|
||||
hourlyRateMax?: number;
|
||||
hourly_rate_min?: number;
|
||||
hourly_rate_max?: number;
|
||||
availability?: string;
|
||||
durationDays?: number;
|
||||
duration_days?: number;
|
||||
};
|
||||
|
||||
async function fetchJob(id: string): Promise<Job | null> {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/jobs/${id}`);
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data.job || data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function JobDetailPage() {
|
||||
const params = useParams();
|
||||
const [job] = createResource(() => params.id, fetchJob);
|
||||
|
||||
const skills = createMemo(() => job()?.requiredSkills || job()?.required_skills || []);
|
||||
const client = createMemo(() => job()?.clientName || job()?.client_name || job()?.companyName || job()?.company_name || '—');
|
||||
const exp = createMemo(() => job()?.experienceLevel || job()?.experience_level || '—');
|
||||
const rateMin = createMemo(() => job()?.hourlyRateMin ?? job()?.hourly_rate_min);
|
||||
const rateMax = createMemo(() => job()?.hourlyRateMax ?? job()?.hourly_rate_max);
|
||||
const duration = createMemo(() => job()?.durationDays ?? job()?.duration_days);
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Job Management</h1>
|
||||
<p class="page-subtitle">Review one live backend job in the same detail-first style as other admin modules.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/jobs">Back to Jobs</A>
|
||||
</div>
|
||||
|
||||
<Show when={job.loading}>
|
||||
<div class="card"><p class="notice">Loading job...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!job.loading && !job()}>
|
||||
<div class="card"><p class="notice">Job not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={job()}>
|
||||
<section class="card">
|
||||
<div class="field-grid-2">
|
||||
<div>
|
||||
<p class="hint">Title</p>
|
||||
<p style="margin:6px 0 0;font-weight:700;color:#0f172a">{job()!.title || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="hint">Status</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{job()!.status || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="hint">Client</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{client()}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="hint">Experience</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{exp()}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="hint">Rate</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{rateMin() != null ? `₹${rateMin()} - ₹${rateMax() ?? rateMin()}` : '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="hint">Location</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{job()!.location || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="hint">Availability</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{job()!.availability || '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="hint">Duration</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{duration() != null ? `${duration()} days` : '—'}</p>
|
||||
</div>
|
||||
<div style="grid-column:1/-1">
|
||||
<p class="hint">Required Skills</p>
|
||||
<p style="margin:6px 0 0;color:#334155">{skills().length > 0 ? skills().join(', ') : '—'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:18px">
|
||||
<p class="hint">Description</p>
|
||||
<p style="margin:6px 0 0;color:#334155;white-space:pre-wrap">{job()!.description || '—'}</p>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
101
src/routes/admin/verification-status.tsx
Normal file
101
src/routes/admin/verification-status.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { A } from '@solidjs/router';
|
||||
import { createMemo, createResource, For, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
type ApprovalRow = {
|
||||
id: string;
|
||||
requestType?: string;
|
||||
type?: string;
|
||||
requestStatus?: string;
|
||||
status?: string;
|
||||
requester?: { name?: string; email?: string };
|
||||
requesterName?: string;
|
||||
requesterEmail?: string;
|
||||
createdAt?: string;
|
||||
created_at?: string;
|
||||
};
|
||||
|
||||
async function fetchApprovals(): Promise<ApprovalRow[]> {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/admin/approvals`);
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return Array.isArray(data) ? data : (data.approvals || []);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default function VerificationStatusPage() {
|
||||
const [rows] = createResource(fetchApprovals);
|
||||
|
||||
const normalized = createMemo(() => (rows() || []).map((r) => ({
|
||||
...r,
|
||||
status: (r.requestStatus || r.status || 'UNKNOWN').toUpperCase(),
|
||||
type: (r.requestType || r.type || 'PROFILE').toUpperCase(),
|
||||
requesterName: r.requester?.name || r.requesterName || 'Unknown',
|
||||
requesterEmail: r.requester?.email || r.requesterEmail || '—',
|
||||
createdAt: r.createdAt || r.created_at || '',
|
||||
})));
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Verification Status</h1>
|
||||
<p class="page-subtitle">Track request status states and open a specific record for follow-up.</p>
|
||||
</div>
|
||||
<A class="btn" href="/admin/approval">Open Approval Center</A>
|
||||
</div>
|
||||
|
||||
<section class="card" style="padding:0;overflow:hidden">
|
||||
<div class="table-wrap">
|
||||
<table class="list-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Type</th>
|
||||
<th>Requester</th>
|
||||
<th>Status</th>
|
||||
<th>Submitted</th>
|
||||
<th class="align-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={rows.loading}>
|
||||
<tr><td colspan="6" style="text-align:center;padding:32px;color:#64748b">Loading verification statuses...</td></tr>
|
||||
</Show>
|
||||
<Show when={!rows.loading && normalized().length === 0}>
|
||||
<tr><td colspan="6" style="text-align:center;padding:32px;color:#94a3b8">No verification status records found.</td></tr>
|
||||
</Show>
|
||||
<Show when={!rows.loading && normalized().length > 0}>
|
||||
<For each={normalized()}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<td style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:#64748b">{item.id.slice(0, 8)}...</td>
|
||||
<td style="color:#475569">{item.type}</td>
|
||||
<td>
|
||||
<div style="font-weight:600;color:#0f172a">{item.requesterName}</div>
|
||||
<div style="font-size:12px;color:#64748b">{item.requesterEmail}</div>
|
||||
</td>
|
||||
<td><span class="status-chip">{item.status}</span></td>
|
||||
<td style="color:#475569">{item.createdAt ? new Date(item.createdAt).toLocaleString() : '—'}</td>
|
||||
<td>
|
||||
<div class="table-actions">
|
||||
<A class="action-icon-btn" href={`/admin/verification-status/${item.id}`} title="Open Status Detail">↗</A>
|
||||
<A class="action-icon-btn" href={`/admin/approval/${item.id}`} title="Open Approval Detail">👁</A>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
84
src/routes/admin/verification-status/[id].tsx
Normal file
84
src/routes/admin/verification-status/[id].tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { A, useParams } from '@solidjs/router';
|
||||
import { createMemo, createResource, 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;
|
||||
requester?: { name?: string; email?: string };
|
||||
requesterName?: string;
|
||||
requesterEmail?: string;
|
||||
createdAt?: string;
|
||||
created_at?: string;
|
||||
requestReason?: string;
|
||||
};
|
||||
|
||||
async function fetchApproval(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 VerificationStatusDetailPage() {
|
||||
const params = useParams();
|
||||
const [detail] = createResource(() => params.id, fetchApproval);
|
||||
|
||||
const status = createMemo(() => (detail()?.requestStatus || detail()?.status || 'UNKNOWN').toUpperCase());
|
||||
const type = createMemo(() => (detail()?.requestType || detail()?.type || 'PROFILE').toUpperCase());
|
||||
const requester = createMemo(() => detail()?.requester?.name || detail()?.requesterName || 'Unknown');
|
||||
const email = createMemo(() => detail()?.requester?.email || detail()?.requesterEmail || '—');
|
||||
const submitted = createMemo(() => detail()?.createdAt || detail()?.created_at || '');
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Verification Status Detail</h1>
|
||||
<p class="page-subtitle">Open one verification status request and jump into approval review when needed.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/verification-status">Back to Status List</A>
|
||||
<A class="btn navy" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={detail.loading}>
|
||||
<div class="card"><p class="notice">Loading verification status...</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={!detail.loading && !detail()}>
|
||||
<div class="card"><p class="notice">Verification status record not found.</p></div>
|
||||
</Show>
|
||||
|
||||
<Show when={detail()}>
|
||||
<div class="grid" style="margin-top:0">
|
||||
<section class="card">
|
||||
<h2 style="margin-bottom:8px">Request</h2>
|
||||
<p class="notice" style="margin:0"><strong>ID:</strong> {detail()!.id}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Status:</strong> {status()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Submitted:</strong> {submitted() ? new Date(submitted()).toLocaleString() : '—'}</p>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2 style="margin-bottom:8px">Requester</h2>
|
||||
<p class="notice" style="margin:0"><strong>Name:</strong> {requester()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p>
|
||||
</section>
|
||||
</div>
|
||||
<section class="card" style="margin-top:16px">
|
||||
<h2 style="margin-bottom:10px">Request Reason Payload</h2>
|
||||
<pre class="json">{detail()!.requestReason || 'No requestReason payload found.'}</pre>
|
||||
</section>
|
||||
</Show>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
24
src/routes/admin/verification.tsx
Normal file
24
src/routes/admin/verification.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { A } from '@solidjs/router';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
export default function VerificationManagementPage() {
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="page-title">Approval Management</h1>
|
||||
<p class="page-subtitle" style="margin-top:8px">
|
||||
Admin review now lives under Approval Management. Verification remains a user-facing status concept.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section class="card">
|
||||
<p class="notice" style="margin:0">
|
||||
Use Approval Management to review companies, customers, candidates, and professional submissions.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<A class="btn navy" href="/admin/approval">Open Approval Management</A>
|
||||
</div>
|
||||
</section>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
80
src/routes/admin/verification/[id].tsx
Normal file
80
src/routes/admin/verification/[id].tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { A, useParams } from '@solidjs/router';
|
||||
import { createMemo, createResource, Show } from 'solid-js';
|
||||
import AdminShell from '~/components/AdminShell';
|
||||
|
||||
const API = '/api/gateway';
|
||||
|
||||
type Approval = {
|
||||
id: string;
|
||||
requestStatus?: string;
|
||||
status?: string;
|
||||
requestType?: string;
|
||||
type?: string;
|
||||
requester?: { name?: string; email?: string };
|
||||
requesterName?: string;
|
||||
requesterEmail?: string;
|
||||
requestReason?: string;
|
||||
};
|
||||
|
||||
async function fetchApproval(id: string): Promise<Approval | 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 VerificationDetailPage() {
|
||||
const params = useParams();
|
||||
const [approval] = createResource(() => params.id, fetchApproval);
|
||||
|
||||
const status = createMemo(() => (approval()?.requestStatus || approval()?.status || 'PENDING').toUpperCase());
|
||||
const type = createMemo(() => (approval()?.requestType || approval()?.type || 'PROFILE').toUpperCase());
|
||||
const requester = createMemo(() => approval()?.requester?.name || approval()?.requesterName || 'Unknown');
|
||||
const email = createMemo(() => approval()?.requester?.email || approval()?.requesterEmail || '—');
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card page-actions">
|
||||
<div>
|
||||
<h1 class="page-title">Verification Review</h1>
|
||||
<p class="page-subtitle">Review submission context, documents, and verification decision state.</p>
|
||||
</div>
|
||||
<div class="page-actions-right">
|
||||
<A class="btn" href="/admin/verification">Back to Verification</A>
|
||||
<A class="btn navy" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={approval.loading}>
|
||||
<div class="card"><p class="notice">Loading verification detail...</p></div>
|
||||
</Show>
|
||||
<Show when={!approval.loading && !approval()}>
|
||||
<div class="card"><p class="notice">Verification request not found.</p></div>
|
||||
</Show>
|
||||
<Show when={approval()}>
|
||||
<div class="grid" style="margin-top:0">
|
||||
<section class="card">
|
||||
<h2 style="margin-bottom:8px">Summary</h2>
|
||||
<p class="notice" style="margin:0"><strong>Approval ID:</strong> {approval()!.id}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Type:</strong> {type()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Status:</strong> {status()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Requester:</strong> {requester()}</p>
|
||||
<p class="notice" style="margin:8px 0 0"><strong>Email:</strong> {email()}</p>
|
||||
</section>
|
||||
<section class="card">
|
||||
<h2 style="margin-bottom:8px">Remark Snapshot</h2>
|
||||
<p class="notice" style="margin:0">
|
||||
This route mirrors the Next.js verification detail entry point and delegates action workflow to Approval Management.
|
||||
</p>
|
||||
<div class="actions">
|
||||
<A class="btn primary" href={`/admin/approval/${params.id}`}>Review & Take Action</A>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue