diff --git a/src/app.css b/src/app.css index 024ccaa..71d3386 100644 --- a/src/app.css +++ b/src/app.css @@ -1214,6 +1214,26 @@ body { gap: 16px; } +.modal-backdrop { + position: fixed; + inset: 0; + z-index: 60; + background: rgba(2, 6, 23, 0.55); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.modal { + width: min(560px, 100%); + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 14px; + box-shadow: 0 24px 60px -28px rgba(15, 23, 42, 0.6); + padding: 20px; +} + .sub-card { border: 1px solid #e2e8f0; border-radius: 12px; diff --git a/src/routes/admin/applications.tsx b/src/routes/admin/applications.tsx index d756c2a..2582327 100644 --- a/src/routes/admin/applications.tsx +++ b/src/routes/admin/applications.tsx @@ -1,9 +1,161 @@ -import { onMount } from 'solid-js'; -import { useNavigate } from '@solidjs/router'; +import { createMemo, createResource, createSignal, For, Show } from 'solid-js'; import AdminShell from '~/components/AdminShell'; -export default function ApplicationsAliasPage() { - const navigate = useNavigate(); - onMount(() => navigate('/admin/jobs', { replace: true })); - return

Redirecting to jobs management...

; +const API = '/api/gateway'; + +type ApplicationRow = { + id: string; + requirementId: string; + professionalId: string; + status: string; + quote?: number; + message?: string; + createdAt?: string; +}; + +type Requirement = { + id: string; + title?: string; + location?: string; + profession?: string; + customerId?: string; +}; + +type ApplicationsPayload = { + applications: ApplicationRow[]; + requirements: Requirement[]; +}; + +async function fetchApplications(): Promise { + const [appRes, reqRes] = await Promise.all([ + fetch(`${API}/api/responses`), + fetch(`${API}/api/requirements`), + ]); + + const appData = appRes.ok ? await appRes.json() : {}; + const reqData = reqRes.ok ? await reqRes.json() : {}; + + return { + applications: appData?.responses || appData?.applications || [], + requirements: reqData?.requirements || [], + }; +} + +export default function ApplicationsPage() { + const [refreshToken, setRefreshToken] = createSignal(0); + const [busyId, setBusyId] = createSignal(''); + const [error, setError] = createSignal(''); + + const [payload] = createResource(refreshToken, fetchApplications); + const applications = createMemo(() => payload()?.applications || []); + const requirements = createMemo(() => payload()?.requirements || []); + + async function updateStatus(id: string, status: string) { + setBusyId(id); + setError(''); + try { + const res = await fetch(`${API}/api/responses/${id}/status`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status }), + }); + if (!res.ok) { + const message = await res.text(); + throw new Error(message || 'Failed to update status'); + } + setRefreshToken((value) => value + 1); + } catch (nextError: any) { + setError(nextError?.message || 'Failed to update status'); + } finally { + setBusyId(''); + } + } + + function requirementFor(id: string): Requirement | undefined { + return requirements().find((item) => item.id === id); + } + + function statusClass(status: string): string { + if (status === 'ACCEPTED') return 'status-approved'; + if (status === 'REJECTED' || status === 'WITHDRAWN') return 'status-rejected'; + return 'status-pending'; + } + + return ( + +
+

Applications

+

Review submitted applications and update acceptance status.

+
+ + +

Loading applications...

+
+ + +

{error()}

+
+ + +

No applications found.

+
+ +
+ + {(app) => { + const req = createMemo(() => requirementFor(app.requirementId)); + return ( +
+
+
+

{req()?.title || 'Unknown Requirement'}

+

ID: {app.id}

+
+ {app.status} +
+ +
+

Message

+

{app.message || 'No message provided.'}

+
+ +
+
+

Quote

+

₹ {app.quote || 0}

+
+
+

Category

+

{req()?.profession || '—'}

+
+
+

Location

+

{req()?.location || '—'}

+
+
+

Applied On

+

{app.createdAt ? new Date(app.createdAt).toLocaleDateString() : '—'}

+
+
+ +
+ + + + + + + + + + + +
+
+ ); + }} +
+
+
+ ); } diff --git a/src/routes/admin/modules.tsx b/src/routes/admin/modules.tsx index e044420..29574c8 100644 --- a/src/routes/admin/modules.tsx +++ b/src/routes/admin/modules.tsx @@ -1,9 +1,225 @@ -import { onMount } from 'solid-js'; -import { useNavigate } from '@solidjs/router'; +import { createMemo, createResource, createSignal, For, Show } from 'solid-js'; import AdminShell from '~/components/AdminShell'; -export default function ModulesAliasPage() { - const navigate = useNavigate(); - onMount(() => navigate('/admin/internal-dashboard-management', { replace: true })); - return

Redirecting to dashboard module configuration...

; +const API = '/api/gateway'; + +type ModuleRecord = { + id: string; + name: string; + key: string; + description?: string; + isActive: boolean; +}; + +type ModuleForm = { + name: string; + key: string; + description: string; + isActive: boolean; +}; + +const EMPTY_FORM: ModuleForm = { name: '', key: '', description: '', isActive: true }; + +async function fetchModules(): Promise { + const res = await fetch(`${API}/api/modules`); + if (!res.ok) return []; + const data = await res.json(); + return Array.isArray(data) ? data : data?.modules || []; +} + +export default function ModulesPage() { + const [refreshToken, setRefreshToken] = createSignal(0); + const [isModalOpen, setIsModalOpen] = createSignal(false); + const [editing, setEditing] = createSignal(null); + const [form, setForm] = createSignal({ ...EMPTY_FORM }); + const [error, setError] = createSignal(''); + const [submitting, setSubmitting] = createSignal(false); + + const [modules] = createResource(refreshToken, fetchModules); + const modalTitle = createMemo(() => editing() ? 'Edit Module' : 'Create Module'); + + function openModal(item?: ModuleRecord) { + if (item) { + setEditing(item); + setForm({ + name: item.name || '', + key: item.key || '', + description: item.description || '', + isActive: Boolean(item.isActive), + }); + } else { + setEditing(null); + setForm({ ...EMPTY_FORM }); + } + setError(''); + setIsModalOpen(true); + } + + function closeModal() { + setIsModalOpen(false); + setEditing(null); + setForm({ ...EMPTY_FORM }); + setError(''); + } + + async function submitForm(event: Event) { + event.preventDefault(); + const current = form(); + if (!current.name.trim() || !current.key.trim()) { + setError('Name and key are required.'); + return; + } + + setSubmitting(true); + setError(''); + try { + const target = editing(); + const method = target ? 'PATCH' : 'POST'; + const url = target ? `${API}/api/modules/${target.id}` : `${API}/api/modules`; + const res = await fetch(url, { + method, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(current), + }); + if (!res.ok) { + const message = await res.text(); + throw new Error(message || 'Failed to save module'); + } + closeModal(); + setRefreshToken((value) => value + 1); + } catch (nextError: any) { + setError(nextError?.message || 'Failed to save module'); + } finally { + setSubmitting(false); + } + } + + async function removeModule(id: string) { + if (!confirm('Are you sure you want to delete this module?')) return; + try { + const res = await fetch(`${API}/api/modules/${id}`, { method: 'DELETE' }); + if (!res.ok) throw new Error('Delete failed'); + setRefreshToken((value) => value + 1); + } catch { + setError('Failed to delete module.'); + } + } + + return ( + +
+
+

Module Registry

+

Manage internal module definitions and activation state.

+
+ +
+ + +

{error()}

+
+ + +

Loading modules...

+
+ +
+
+ + + + + + + + + + + + 0} fallback={ + + }> + + {(item) => ( + + + + + + + + )} + + + +
NameKeyDescriptionStatusActions
No modules found.
{item.name}{item.key}{item.description || '—'} + + {item.isActive ? 'Active' : 'Inactive'} + + +
+ + +
+
+
+
+ + +