diff --git a/src/components/admin/ProfessionAdminListPage.tsx b/src/components/admin/ProfessionAdminListPage.tsx new file mode 100644 index 0000000..18b1be1 --- /dev/null +++ b/src/components/admin/ProfessionAdminListPage.tsx @@ -0,0 +1,229 @@ +import { A } from '@solidjs/router'; +import { createMemo, createResource, createSignal, For, Show } from 'solid-js'; + +const API = '/api/gateway'; + +type SortMode = 'newest' | 'oldest' | 'name_asc' | 'name_desc'; + +async function fetchProfessionList(endpoint: string): Promise { + try { + const res = await fetch(`${API}${endpoint}`); + if (!res.ok) throw new Error(`Failed to load: ${res.status}`); + const data = await res.json(); + return Array.isArray(data) ? data : []; + } catch (e) { + console.error(`Failed to fetch from ${endpoint}:`, e); + return []; + } +} + +function statusBadge(status?: string) { + const normalized = (status || '').toUpperCase(); + if (normalized === 'ACTIVE') return 'inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-700'; + if (normalized === 'PENDING') return 'inline-flex items-center rounded-full bg-orange-50 px-2.5 py-0.5 text-xs font-medium text-orange-700'; + return 'inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600'; +} + +export default function ProfessionAdminListPage(props: { + endpoint: string; + title: string; + subtitle: string; + emptyLabel: string; + viewHref: (id: string) => string; + nameField?: string; +}) { + const nameField = props.nameField || 'first_name'; + const [items] = createResource(() => props.endpoint, fetchProfessionList); + const [search, setSearch] = createSignal(''); + const [statusFilter, setStatusFilter] = createSignal<'all' | 'ACTIVE' | 'INACTIVE' | 'PENDING'>('all'); + const [sortBy, setSortBy] = createSignal('newest'); + const [sortMenuOpen, setSortMenuOpen] = createSignal(false); + const [filterMenuOpen, setFilterMenuOpen] = createSignal(false); + + const filtered = createMemo(() => { + const list = items() ?? []; + const q = search().toLowerCase().trim(); + const f = statusFilter(); + const sorted = list.filter((u) => { + const firstName = String(u.first_name || '').toLowerCase(); + const lastName = String(u.last_name || '').toLowerCase(); + const fullName = `${firstName} ${lastName}`.trim(); + const email = String(u.email || '').toLowerCase(); + const st = String(u.status || '').toUpperCase(); + const matchesSearch = !q || fullName.includes(q) || email.includes(q); + const matchesStatus = f === 'all' || st === f; + return matchesSearch && matchesStatus; + }); + + sorted.sort((a, b) => { + const aDate = new Date(a.created_at || 0).getTime(); + const bDate = new Date(b.created_at || 0).getTime(); + if (sortBy() === 'oldest') return aDate - bDate; + if (sortBy() === 'name_asc') return `${a.first_name || ''} ${a.last_name || ''}`.localeCompare(`${b.first_name || ''} ${b.last_name || ''}`); + if (sortBy() === 'name_desc') return `${b.first_name || ''} ${b.last_name || ''}`.localeCompare(`${a.first_name || ''} ${a.last_name || ''}`); + return bDate - aDate; + }); + + return sorted; + }); + + const exportCsv = () => { + const headers = ['First Name', 'Last Name', 'Email', 'Phone', 'Status', 'Registered']; + const rows = filtered().map((item) => [ + String(item.first_name || ''), + String(item.last_name || ''), + String(item.email || ''), + String(item.phone || ''), + String(item.status || ''), + item.created_at ? new Date(item.created_at).toLocaleDateString() : '', + ]); + const csv = [headers, ...rows] + .map((line) => line.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')) + .join('\n'); + + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `${props.title.toLowerCase().replace(/\s+/g, '-')}-export.csv`; + link.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+
+

{props.title}

+

{props.subtitle}

+
+ +
+
+ setSearch(e.currentTarget.value)} + class="rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#FF5E13] w-72" + /> + +
+ + +
+ + {(item) => ( + + )} + +
+
+
+ +
+ + +
+ + {(item) => ( + + )} + +
+
+
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + 0}> + + {(item) => ( + + + + + + + + + )} + + + +
NameEmailPhoneStatusRegisteredActions
Loading...
Failed to load. Is the backend running?
{props.emptyLabel}
{item.first_name || ''} {item.last_name || ''}{item.email}{item.phone || '—'} + {item.status?.toUpperCase() || '—'} + {item.created_at ? new Date(item.created_at).toLocaleDateString() : '—'} +
+ View +
+
+
+
+
+
+ ); +} diff --git a/src/routes/admin/catering-services.tsx b/src/routes/admin/catering-services.tsx index 349e94b..0432d75 100644 --- a/src/routes/admin/catering-services.tsx +++ b/src/routes/admin/catering-services.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function CateringServicesPage() { +export default function Page() { return ( - `/admin/users/${id}`} + emptyLabel="No catering services found." + viewHref={(id) => `/admin/catering-services/${id}`} /> ); } diff --git a/src/routes/admin/developers.tsx b/src/routes/admin/developers.tsx index 32fa751..27b5203 100644 --- a/src/routes/admin/developers.tsx +++ b/src/routes/admin/developers.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function DevelopersPage() { +export default function Page() { return ( - `/admin/users/${id}`} + emptyLabel="No developers found." + viewHref={(id) => `/admin/developers/${id}`} /> ); } diff --git a/src/routes/admin/fitness-trainers.tsx b/src/routes/admin/fitness-trainers.tsx index 3af2562..8942307 100644 --- a/src/routes/admin/fitness-trainers.tsx +++ b/src/routes/admin/fitness-trainers.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function FitnessTrainersPage() { +export default function Page() { return ( - `/admin/users/${id}`} + emptyLabel="No fitness trainers found." + viewHref={(id) => `/admin/fitness-trainers/${id}`} /> ); } diff --git a/src/routes/admin/graphic-designers.tsx b/src/routes/admin/graphic-designers.tsx index 4057319..1ab794e 100644 --- a/src/routes/admin/graphic-designers.tsx +++ b/src/routes/admin/graphic-designers.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function GraphicDesignersPage() { +export default function Page() { return ( - `/admin/users/${id}`} + subtitle="Manage all graphic designer accounts on the platform." + emptyLabel="No graphic designers found." + viewHref={(id) => `/admin/graphic-designers/${id}`} /> ); } diff --git a/src/routes/admin/makeup-artist.tsx b/src/routes/admin/makeup-artist.tsx index 50f7137..8c602ea 100644 --- a/src/routes/admin/makeup-artist.tsx +++ b/src/routes/admin/makeup-artist.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function MakeupArtistPage() { +export default function Page() { return ( - `/admin/users/${id}`} + emptyLabel="No makeup artists found." + viewHref={(id) => `/admin/makeup-artist/${id}`} /> ); } diff --git a/src/routes/admin/onboarding-schemas/index.tsx b/src/routes/admin/onboarding-schemas/index.tsx index 985d48d..e432dde 100644 --- a/src/routes/admin/onboarding-schemas/index.tsx +++ b/src/routes/admin/onboarding-schemas/index.tsx @@ -58,187 +58,8 @@ type OnboardingSchema = { updatedAt: string; }; -const FALLBACK_SCHEMAS: OnboardingSchema[] = [ - { - id: 'os1', - name: 'Professional Photographer Flow', - code: 'ONB-PRO-PHOTO', - userType: 'PROFESSIONAL', - role: 'Photographer', - description: 'Onboarding for professional photographers (India localization).', - verificationType: 'IDENTITY', - requiredDocuments: ['Aadhaar Card', 'PAN Card', 'Portfolio Sample'], - steps: [ - { - id: 'st_role', - name: 'Choose Role', - description: 'Confirm your onboarding role and specialization.', - required: true, - fields: [ - { id: 'f_role', key: 'role', label: 'Professional Role', type: 'select', required: true, options: ['Photographer', 'Makeup Artist', 'Tutor', 'Developer', 'Video Editor', 'UGC Content Creator'] }, - { id: 'f_special', key: 'specialization', label: 'Specialization', type: 'select', required: true, options: ['Wedding', 'Product', 'Corporate', 'Portrait', 'Events'] }, - ], - }, - { - id: 'st_profile', - name: 'Profile Details', - description: 'Tell us about your profile.', - required: true, - fields: [ - { id: 'f_name', key: 'full_name', label: 'Full Name', type: 'text', required: true, placeholder: 'e.g., Ashwin Kumar' }, - { id: 'f_email', key: 'email', label: 'Email', type: 'email', required: true, placeholder: 'e.g., ashwin@example.com' }, - { id: 'f_phone', key: 'phone', label: 'Phone Number', type: 'phone', required: true, placeholder: 'e.g., +91 98765 43210' }, - ], - }, - { - id: 'st_location', - name: 'Contact & Location', - description: 'Your contact address details.', - required: true, - fields: [ - { id: 'f_city', key: 'city', label: 'City', type: 'text', required: true, placeholder: 'e.g., Chennai' }, - { id: 'f_pin', key: 'pincode', label: 'PIN Code', type: 'pincode', required: true, placeholder: 'e.g., 600001' }, - { id: 'f_addr', key: 'address', label: 'Address', type: 'textarea', required: true, placeholder: 'House no, Street, Area' }, - ], - }, - { - id: 'st_portfolio', - name: 'Portfolio & Submission', - description: 'Upload your portfolio sample.', - required: true, - fields: [ - { id: 'f_portfolio', key: 'portfolio_file', label: 'Portfolio File', type: 'file', required: true, allowedFileTypes: ['PDF', 'JPG', 'JPEG', 'PNG'] }, - { id: 'f_about', key: 'about', label: 'About You', type: 'textarea', required: true, placeholder: 'Tell us about your work…' }, - ], - }, - { - id: 'st_identity', - name: 'Identity Verification', - description: 'Upload identity documents for verification.', - required: true, - fields: [ - { id: 'f_aadhaar', key: 'aadhaar', label: 'Aadhaar Card', type: 'file', required: true, allowedFileTypes: ['PDF', 'JPG', 'JPEG', 'PNG'] }, - { id: 'f_pan', key: 'pan', label: 'PAN Card', type: 'file', required: true, allowedFileTypes: ['PDF', 'JPG', 'JPEG', 'PNG'] }, - ], - }, - ], - status: 'ACTIVE', - version: '1.2.0', - updatedAt: '2026-03-27', - }, - { - id: 'os2', - name: 'Basic Company Onboarding', - code: 'ONB-COMP-BASIC', - userType: 'COMPANY', - description: 'Company onboarding with business verification.', - verificationType: 'BUSINESS', - requiredDocuments: ['GST Certificate', 'Company PAN', 'Incorporation Certificate'], - steps: [ - { - id: 'st_comp_identity', - name: 'Company Identity', - description: 'Provide company identity details.', - required: true, - fields: [ - { id: 'f_company_name', key: 'company_name', label: 'Company Name', type: 'text', required: true, placeholder: 'e.g., Tech Solutions Inc' }, - { id: 'f_industry', key: 'industry', label: 'Industry', type: 'select', required: true, options: ['Software', 'Design', 'Logistics', 'Manufacturing', 'Services'] }, - ], - }, - { - id: 'st_comp_contact', - name: 'Contact Details', - description: 'Primary contact information.', - required: true, - fields: [ - { id: 'f_c_email', key: 'email', label: 'Company Email', type: 'email', required: true, placeholder: 'e.g., hr@company.com' }, - { id: 'f_c_phone', key: 'phone', label: 'Phone Number', type: 'phone', required: true, placeholder: 'e.g., +91 98765 43210' }, - { id: 'f_c_city', key: 'city', label: 'City', type: 'text', required: true, placeholder: 'e.g., Chennai' }, - ], - }, - { - id: 'st_hiring', - name: 'Hiring Preferences', - description: 'Tell us about hiring needs.', - required: true, - fields: [ - { id: 'f_roles', key: 'hire_for', label: 'Hiring For', type: 'multi-select', required: true, options: ['Developers', 'Designers', 'Photographers', 'UGC Content Creators', 'Tutors'] }, - { id: 'f_budget', key: 'budget', label: 'Monthly Hiring Budget (₹)', type: 'number', required: true, placeholder: 'e.g., 50000' }, - ], - }, - { - id: 'st_business', - name: 'Business Verification', - description: 'Upload business documents.', - required: true, - fields: [ - { id: 'f_gst', key: 'gst', label: 'GST Certificate', type: 'file', required: true, allowedFileTypes: ['PDF', 'JPG', 'JPEG', 'PNG'] }, - { id: 'f_comp_pan', key: 'company_pan', label: 'Company PAN', type: 'file', required: true, allowedFileTypes: ['PDF', 'JPG', 'JPEG', 'PNG'] }, - ], - }, - ], - status: 'ACTIVE', - version: '1.0.5', - updatedAt: '2026-03-27', - }, - { - id: 'os3', - name: 'Jobseeker Registration', - code: 'ONB-JOB-DEFAULT', - userType: 'JOBSEEKER', - description: 'Jobseeker registration with identity verification.', - verificationType: 'IDENTITY', - requiredDocuments: ['Aadhaar Card', 'Resume'], - steps: [ - { - id: 'st_basic', - name: 'Basic Profile', - description: 'Fill basic profile details.', - required: true, - fields: [ - { id: 'f_js_name', key: 'full_name', label: 'Full Name', type: 'text', required: true, placeholder: 'e.g., Priya Sharma' }, - { id: 'f_js_email', key: 'email', label: 'Email', type: 'email', required: true, placeholder: 'e.g., priya@example.com' }, - { id: 'f_js_phone', key: 'phone', label: 'Phone Number', type: 'phone', required: true, placeholder: 'e.g., +91 98765 43210' }, - ], - }, - { - id: 'st_pref', - name: 'Job Preferences', - description: 'Your job preferences.', - required: true, - fields: [ - { id: 'f_job_type', key: 'job_type', label: 'Job Type', type: 'select', required: true, options: ['Full-time', 'Part-time', 'Contract', 'Internship'] }, - { id: 'f_location', key: 'preferred_location', label: 'Preferred Location', type: 'text', required: true, placeholder: 'e.g., Chennai' }, - ], - }, - { - id: 'st_docs', - name: 'Documents & Links', - description: 'Upload resume and identity.', - required: true, - fields: [ - { id: 'f_resume', key: 'resume', label: 'Resume', type: 'file', required: true, allowedFileTypes: ['PDF'] }, - { id: 'f_js_aadhaar', key: 'aadhaar', label: 'Aadhaar Card', type: 'file', required: true, allowedFileTypes: ['PDF', 'JPG', 'JPEG', 'PNG'] }, - ], - }, - ], - status: 'DRAFT', - version: '2.0.0-rc', - updatedAt: '2026-03-27', - }, -]; -const FALLBACK_SUBMISSIONS: OnboardingSubmission[] = [ - { id: 'sub_1001', userId: 'u_2001', userName: 'Rohit Mehra', roleKey: 'PROFESSIONAL_PHOTOGRAPHER', status: 'PENDING_APPROVAL', createdAt: '2026-03-25', updatedAt: '2026-03-27' }, - { id: 'sub_1002', userId: 'u_2002', userName: 'Tech Solutions Inc', roleKey: 'COMPANY', status: 'DOCUMENT_REQUESTED', createdAt: '2026-03-20', updatedAt: '2026-03-26' }, - { id: 'sub_1003', userId: 'u_2003', userName: 'Priya Sharma', roleKey: 'JOBSEEKER', status: 'SUBMITTED', createdAt: '2026-03-22', updatedAt: '2026-03-22' }, -]; -const FALLBACK_AUDIT: AuditEvent[] = [ - { id: 'evt_1', event: 'ONBOARDING_CREATED', actor: 'Admin', target: 'ONB-PRO-PHOTO', createdAt: '2026-03-01 10:11' }, - { id: 'evt_2', event: 'ONBOARDING_UPDATED', actor: 'Admin', target: 'ONB-COMP-BASIC', createdAt: '2026-03-15 16:40' }, - { id: 'evt_3', event: 'ONBOARDING_PREVIEWED', actor: 'Admin', target: 'ONB-JOB-DEFAULT', createdAt: '2026-03-26 09:20' }, -]; function StatusBadge(props: { status: string }) { const active = () => props.status === 'ACTIVE'; diff --git a/src/routes/admin/photographer.tsx b/src/routes/admin/photographer.tsx index c17a643..916c75b 100644 --- a/src/routes/admin/photographer.tsx +++ b/src/routes/admin/photographer.tsx @@ -1,12 +1,12 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; export default function PhotographerPage() { return ( - `/admin/photographer/${id}`} /> ); diff --git a/src/routes/admin/social-media-managers.tsx b/src/routes/admin/social-media-managers.tsx index b81f570..96ee54e 100644 --- a/src/routes/admin/social-media-managers.tsx +++ b/src/routes/admin/social-media-managers.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function SocialMediaManagersPage() { +export default function Page() { return ( - `/admin/users/${id}`} + emptyLabel="No social media managers found." + viewHref={(id) => `/admin/social-media-managers/${id}`} /> ); } diff --git a/src/routes/admin/tutors.tsx b/src/routes/admin/tutors.tsx index e2bb130..b90a223 100644 --- a/src/routes/admin/tutors.tsx +++ b/src/routes/admin/tutors.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function TutorsPage() { +export default function Page() { return ( - `/admin/users/${id}`} + emptyLabel="No tutors found." + viewHref={(id) => `/admin/tutors/${id}`} /> ); } diff --git a/src/routes/admin/video-editors.tsx b/src/routes/admin/video-editors.tsx index 25801ca..615fbc9 100644 --- a/src/routes/admin/video-editors.tsx +++ b/src/routes/admin/video-editors.tsx @@ -1,13 +1,13 @@ -import RoleUserManagementTablePage from '~/components/admin/RoleUserManagementTablePage'; +import ProfessionAdminListPage from '~/components/admin/ProfessionAdminListPage'; -export default function VideoEditorsPage() { +export default function Page() { return ( - `/admin/users/${id}`} + emptyLabel="No video editors found." + viewHref={(id) => `/admin/video-editors/${id}`} /> ); }