diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 8a07009..969665a 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -64,6 +64,7 @@ + HRM diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 35e4004..864b16e 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -4,6 +4,7 @@ export interface EmployeeRecord { id: string; First_Name: string; Last_Name: string; + Type?: string; Phone_Number: string; Email_Address: string; Voxer_ID: string; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2671d7c..b02c205 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -7,6 +7,8 @@ import { Button } from '$lib/components/ui/button/index.js'; import { Input } from '$lib/components/ui/input/index.js'; import { Label } from '$lib/components/ui/label/index.js'; + import { Badge } from '$lib/components/ui/badge/index.js'; + import * as Select from '$lib/components/ui/select/index.js'; import type { EmployeeRecord } from './+page.server'; import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical'; import PencilIcon from '@lucide/svelte/icons/pencil'; @@ -20,19 +22,37 @@ let loading = $state(true); let loadingMore = $state(false); let error = $state(null); + type TypeFilterValue = 'all' | 'Employee' | 'Sub-Contractor'; + let typeFilter = $state('all'); let searchQuery = $state(''); let nextPage = $state(2); let searchMode = $state(false); // true when showing server-side search results let searchDebounceId: ReturnType | null = null; + function typeParam(): string { + return typeFilter === 'all' ? '' : `&type=${encodeURIComponent(typeFilter)}`; + } + + function setTypeFilter(value: TypeFilterValue) { + typeFilter = value; + loading = true; + if (searchQuery.trim()) { + searchEmployees(searchQuery); + } else { + loadEmployees(); + } + } + let openCreate = $state(false); let openEdit = $state(false); let openDelete = $state(false); let editingEmployee = $state(null); let deletingEmployee = $state(null); + const TYPE_OPTIONS = ['Employee', 'Sub-Contractor', 'Former-Employee', 'Applicant', 'Blacklisted']; let formFirst = $state(''); let formLast = $state(''); + let formType = $state(''); let formPhone = $state(''); let formEmail = $state(''); let formVoxer = $state(''); @@ -45,7 +65,7 @@ async function loadEmployees() { searchMode = false; try { - const res = await fetch('/api/employees?page=1&perPage=200'); + const res = await fetch(`/api/employees?page=1&perPage=200${typeParam()}`); if (!res.ok) throw new Error(res.statusText); const json = await res.json(); employees = json.employees ?? []; @@ -69,7 +89,7 @@ error = null; try { const res = await fetch( - `/api/employees?page=1&perPage=500&search=${encodeURIComponent(query.trim())}` + `/api/employees?page=1&perPage=500&search=${encodeURIComponent(query.trim())}${typeParam()}` ); if (!res.ok) throw new Error(res.statusText); const json = await res.json(); @@ -104,7 +124,7 @@ if (loadingMore || employees.length >= total) return; loadingMore = true; try { - const res = await fetch(`/api/employees?page=${nextPage}&perPage=100`); + const res = await fetch(`/api/employees?page=${nextPage}&perPage=100${typeParam()}`); if (!res.ok) throw new Error(res.statusText); const json = await res.json(); const list = json.employees ?? []; @@ -120,6 +140,7 @@ function resetForm() { formFirst = ''; formLast = ''; + formType = ''; formPhone = ''; formEmail = ''; formVoxer = ''; @@ -137,6 +158,7 @@ editingEmployee = emp; formFirst = emp.First_Name ?? ''; formLast = emp.Last_Name ?? ''; + formType = emp.Type ?? ''; formPhone = emp.Phone_Number ?? ''; formEmail = emp.Email_Address ?? ''; formVoxer = emp.Voxer_ID ?? ''; @@ -161,6 +183,7 @@ body: JSON.stringify({ First_Name: formFirst, Last_Name: formLast, + Type: formType || undefined, Phone_Number: formPhone, Email_Address: formEmail, Voxer_ID: formVoxer, @@ -191,6 +214,7 @@ body: JSON.stringify({ First_Name: formFirst, Last_Name: formLast, + Type: formType || undefined, Phone_Number: formPhone, Email_Address: formEmail, Voxer_ID: formVoxer, @@ -254,6 +278,39 @@
+
+ + + +
First Name Last Name + Type Status Phone Email @@ -288,13 +346,13 @@ {#if loading} - + Loading… {:else if error} - + {error} @@ -309,6 +367,13 @@ > {employee.First_Name} {employee.Last_Name} + + {#if employee.Type} + {employee.Type} + {:else} + + {/if} + {employee.Status} {employee.Phone_Number} {employee.Email_Address} @@ -349,7 +414,7 @@ {:else} - + {searchQuery.trim() ? 'No matching employees.' : 'No employee records found.'} @@ -397,6 +462,20 @@
+
+ + + + {formType || 'Select type…'} + + + (none) + {#each TYPE_OPTIONS as opt} + {opt} + {/each} + + +
@@ -452,6 +531,20 @@
+
+ + + + {formType || 'Select type…'} + + + (none) + {#each TYPE_OPTIONS as opt} + {opt} + {/each} + + +
diff --git a/src/routes/[id]/+page.server.ts b/src/routes/[id]/+page.server.ts index bfe1516..d5b4903 100644 --- a/src/routes/[id]/+page.server.ts +++ b/src/routes/[id]/+page.server.ts @@ -8,6 +8,7 @@ export interface EmployeeRecord { id: string; First_Name: string; Last_Name: string; + Type?: string; Phone_Number: string; Email_Address: string; Voxer_ID: string; diff --git a/src/routes/[id]/+page.svelte b/src/routes/[id]/+page.svelte index ba51b16..610aea8 100644 --- a/src/routes/[id]/+page.svelte +++ b/src/routes/[id]/+page.svelte @@ -6,7 +6,7 @@ import { Label } from '$lib/components/ui/label/index.js'; import * as Dialog from '$lib/components/ui/dialog/index.js'; import * as Card from '$lib/components/ui/card/index.js'; - import { badgeVariants } from '$lib/components/ui/badge/index.js'; + import { Badge, badgeVariants } from '$lib/components/ui/badge/index.js'; import type { EmployeeRecord, EmployeeReportRecord, @@ -270,8 +270,11 @@ -

+

{employee?.First_Name} {employee?.Last_Name} + {#if employee?.Type} + {employee.Type} + {/if}

{#if employee}
@@ -325,6 +328,38 @@
{employee.Last_Name || '—'}
{/if}
+
+ + {#if editMode} + { + employee = { ...employee!, Type: v ?? undefined }; + if (v !== undefined) patchField('Type', v ?? ''); + }} + disabled={patchSaving} + > + + {employee.Type || 'Select type…'} + + + (none) + {#each ['Employee', 'Sub-Contractor', 'Former-Employee', 'Applicant', 'Blacklisted'] as opt} + {opt} + {/each} + + + {:else} +
+ {#if employee.Type} + {employee.Type} + {:else} + — + {/if} +
+ {/if} +
{#if editMode} diff --git a/src/routes/api/employees/+server.ts b/src/routes/api/employees/+server.ts index 963d1b2..9f9fb54 100644 --- a/src/routes/api/employees/+server.ts +++ b/src/routes/api/employees/+server.ts @@ -5,6 +5,7 @@ export interface EmployeeRecord { id: string; First_Name: string; Last_Name: string; + Type?: string; Phone_Number: string; Email_Address: string; Voxer_ID: string; @@ -21,21 +22,26 @@ function escapeFilterValue(s: string): string { export const GET: RequestHandler = async ({ locals, url }) => { const search = url.searchParams.get('search')?.trim() ?? ''; + const typeFilter = url.searchParams.get('type')?.trim() ?? ''; const page = Math.max(1, parseInt(url.searchParams.get('page') ?? '1', 10)); const perPage = Math.min(500, Math.max(1, parseInt(url.searchParams.get('perPage') ?? '200', 10))); - let filter = 'delete != true'; + const typeClause = + typeFilter === 'Employee' || typeFilter === 'Sub-Contractor' + ? `(Type = "${escapeFilterValue(typeFilter)}") && ` + : ''; + let filter = `${typeClause}delete != true`; if (search.length > 0) { const escaped = escapeFilterValue(search); - // Search across main text fields (PocketBase ~ is "contains") - filter = `(${[ + const searchFilter = `(${[ `First_Name ~ "${escaped}"`, `Last_Name ~ "${escaped}"`, `Email_Address ~ "${escaped}"`, `Phone_Number ~ "${escaped}"`, `Voxer_ID ~ "${escaped}"`, `Status ~ "${escaped}"` - ].join(' || ')}) && delete != true`; + ].join(' || ')})`; + filter = `${typeClause}${searchFilter} && delete != true`; } const { items, totalItems } = await locals.pb @@ -50,9 +56,10 @@ export const GET: RequestHandler = async ({ locals, url }) => { export const POST: RequestHandler = async ({ request, locals }) => { const body = await request.json(); - const record = await locals.pb.collection(COLLECTION).create({ + const record = await locals.pb.collection(COLLECTION).create({ First_Name: body.First_Name ?? '', Last_Name: body.Last_Name ?? '', + Type: body.Type ?? undefined, Phone_Number: body.Phone_Number ?? '', Email_Address: body.Email_Address ?? '', Voxer_ID: body.Voxer_ID ?? '', diff --git a/src/routes/api/employees/[id]/+server.ts b/src/routes/api/employees/[id]/+server.ts index c5ff514..89aca0a 100644 --- a/src/routes/api/employees/[id]/+server.ts +++ b/src/routes/api/employees/[id]/+server.ts @@ -30,6 +30,7 @@ export const PATCH: RequestHandler = async ({ params, request, locals }) => { const record = await locals.pb.collection(COLLECTION).update(id, { ...(body.First_Name !== undefined && { First_Name: body.First_Name }), ...(body.Last_Name !== undefined && { Last_Name: body.Last_Name }), + ...(body.Type !== undefined && { Type: body.Type }), ...(body.Phone_Number !== undefined && { Phone_Number: body.Phone_Number }), ...(body.Email_Address !== undefined && { Email_Address: body.Email_Address }), ...(body.Voxer_ID !== undefined && { Voxer_ID: body.Voxer_ID }),