Add svelte-sonner for notifications and enhance employee search functionality

- Integrated svelte-sonner for toast notifications in the layout.
- Implemented server-side search for employees with pagination in the API.
- Updated employee loading logic to support search queries and loading more results.
- Enhanced employee detail view with navigation and improved UI interactions.
- Adjusted cookie handling in hooks for PocketBase authentication.
This commit is contained in:
eewing
2026-02-17 09:13:10 -06:00
parent faae3c1a43
commit 09487bbede
14 changed files with 1172 additions and 32 deletions
+34 -6
View File
@@ -14,12 +14,38 @@ export interface EmployeeRecord {
const COLLECTION = 'Employee_Records';
export const GET: RequestHandler = async ({ locals }) => {
const { items } = await locals.pb
.collection(COLLECTION)
.getList<EmployeeRecord>(1, 500, { sort: 'Last_Name,First_Name' });
/** Escape a string for use inside PocketBase filter double-quotes. */
function escapeFilterValue(s: string): string {
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}
return json({ employees: items });
export const GET: RequestHandler = async ({ locals, url }) => {
const search = url.searchParams.get('search')?.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';
if (search.length > 0) {
const escaped = escapeFilterValue(search);
// Search across main text fields (PocketBase ~ is "contains")
filter = `(${[
`First_Name ~ "${escaped}"`,
`Last_Name ~ "${escaped}"`,
`Email_Address ~ "${escaped}"`,
`Phone_Number ~ "${escaped}"`,
`Voxer_ID ~ "${escaped}"`,
`Status ~ "${escaped}"`
].join(' || ')}) && delete != true`;
}
const { items, totalItems } = await locals.pb
.collection(COLLECTION)
.getList<EmployeeRecord>(page, perPage, {
filter,
sort: 'Last_Name,First_Name'
});
return json({ employees: items, total: totalItems });
};
export const POST: RequestHandler = async ({ request, locals }) => {
@@ -30,7 +56,9 @@ export const POST: RequestHandler = async ({ request, locals }) => {
Phone_Number: body.Phone_Number ?? '',
Email_Address: body.Email_Address ?? '',
Voxer_ID: body.Voxer_ID ?? '',
Status: body.Status ?? ''
Status: body.Status ?? '',
delete: false,
...(locals.user?.id && { entered_by: locals.user.id })
});
return json(record);
};
+28 -3
View File
@@ -4,6 +4,26 @@ import type { EmployeeRecord } from '../+server';
const COLLECTION = 'Employee_Records';
export const GET: RequestHandler = async ({ params, locals }) => {
const id = params.id;
try {
const employee = await locals.pb
.collection(COLLECTION)
.getOne<EmployeeRecord>(id);
if ((employee as { delete?: boolean }).delete === true) {
return json({ error: 'Employee not found' }, { status: 404 });
}
return json({ employee });
} catch (e: unknown) {
const is404 =
e &&
typeof e === 'object' &&
'status' in e &&
(e as { status: number }).status === 404;
return json({ error: 'Employee not found' }, { status: is404 ? 404 : 500 });
}
};
export const PATCH: RequestHandler = async ({ params, request, locals }) => {
const id = params.id;
const body = await request.json();
@@ -13,12 +33,17 @@ export const PATCH: RequestHandler = async ({ params, request, locals }) => {
...(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 }),
...(body.Status !== undefined && { Status: body.Status })
...(body.Status !== undefined && { Status: body.Status }),
...(body.Notes !== undefined && { Notes: body.Notes }),
...(locals.user?.id && { updated_by: locals.user.id })
});
return json(record);
return json({ employee: record });
};
export const DELETE: RequestHandler = async ({ params, locals }) => {
await locals.pb.collection(COLLECTION).delete(params.id);
await locals.pb.collection(COLLECTION).update(params.id, {
delete: true,
...(locals.user?.id && { updated_by: locals.user.id })
});
return new Response(null, { status: 204 });
};
@@ -0,0 +1,44 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
const REPORTS_COLLECTION = 'Employee_Reports';
export interface EmployeeReportRecord {
id: string;
report?: string;
created: string;
updated: string;
[key: string]: unknown;
}
export const GET: RequestHandler = async ({ params, locals }) => {
const employeeId = params.id;
const { items } = await locals.pb
.collection(REPORTS_COLLECTION)
.getList<EmployeeReportRecord>(1, 100, {
filter: `Employee = "${employeeId}" && delete != true`,
sort: '-created'
});
return json({ reports: items });
};
export const POST: RequestHandler = async ({ params, request, locals }) => {
const employeeId = params.id;
const body = await request.json();
let voxers: Array<{ label: string; url: string }> = [];
if (body.voxers != null) {
try {
voxers = Array.isArray(body.voxers) ? body.voxers : JSON.parse(String(body.voxers));
} catch {
voxers = [];
}
}
const report = await locals.pb.collection(REPORTS_COLLECTION).create<EmployeeReportRecord>({
Employee: employeeId,
report: body.report ?? '',
voxers,
delete: false,
...(locals.user?.id && { entered_by: locals.user.id })
});
return json({ report });
};
@@ -0,0 +1,36 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
const REPORTS_COLLECTION = 'Employee_Reports';
export interface EmployeeReportRecord {
id: string;
report?: string;
created: string;
updated: string;
[key: string]: unknown;
}
export const PATCH: RequestHandler = async ({ params, request, locals }) => {
const { reportId } = params;
const body = await request.json();
const update: Record<string, unknown> = {
...(body.report !== undefined && { report: body.report }),
...(locals.user?.id && { updated_by: locals.user.id })
};
if (body.voxers !== undefined) {
update.voxers = Array.isArray(body.voxers) ? body.voxers : JSON.parse(String(body.voxers));
}
const report = await locals.pb
.collection(REPORTS_COLLECTION)
.update<EmployeeReportRecord>(reportId, update);
return json({ report });
};
export const DELETE: RequestHandler = async ({ params, locals }) => {
await locals.pb.collection(REPORTS_COLLECTION).update(params.reportId, {
delete: true,
...(locals.user?.id && { updated_by: locals.user.id })
});
return new Response(null, { status: 204 });
};