import { and, asc, eq, isNull } from 'drizzle-orm'; import { message, superValidate } from 'sveltekit-superforms/server'; import { zod4 } from 'sveltekit-superforms/adapters'; import { db } from '$lib/server/db'; import { bookings, clients, rooms, services } from '$lib/server/db/schema'; import { loadOrganizationContext } from '$lib/server/organizations'; import { archiveSchema } from '$lib/schemas/shared.schema'; import { bookingCreateSchema, bookingEditSchema } from '$lib/schemas/bookings.schema'; import type { Actions, PageServerLoad } from './$types'; async function loadOptions(organizationId: string) { const [clientRows, roomRows, serviceRows] = await Promise.all([ db .select({ id: clients.id, name: clients.name }) .from(clients) .where(and(eq(clients.organizationId, organizationId), isNull(clients.archivedAt))) .orderBy(asc(clients.name)), db .select({ id: rooms.id, name: rooms.name }) .from(rooms) .where(and(eq(rooms.organizationId, organizationId), isNull(rooms.archivedAt))) .orderBy(asc(rooms.name)), db .select({ id: services.id, name: services.name }) .from(services) .where(and(eq(services.organizationId, organizationId), isNull(services.archivedAt))) .orderBy(asc(services.name)) ]); return { clients: clientRows.map((client) => ({ label: client.name, value: client.id })), rooms: roomRows.map((room) => ({ label: room.name, value: room.id })), services: [ { label: 'No service', value: '' }, ...serviceRows.map((service) => ({ label: service.name, value: service.id })) ] }; } export const load: PageServerLoad = async ({ locals, params }) => { const { activeOrganizationId } = await loadOrganizationContext(locals); const records = await db .select() .from(bookings) .where( and( eq(bookings.clientId, params.id), eq(bookings.organizationId, activeOrganizationId), isNull(bookings.archivedAt) ) ) .orderBy(asc(bookings.startsAt)); return { records: records.map((record) => ({ ...record, formValues: { id: record.id, clientId: record.clientId ?? '', roomId: record.roomId ?? '', serviceId: record.serviceId ?? '', startsAt: record.startsAt ?? '', endsAt: record.endsAt ?? '', status: record.status ?? '', notes: record.notes ?? '' } })), options: await loadOptions(activeOrganizationId), createForm: await superValidate({ clientId: params.id }, zod4(bookingCreateSchema), { id: 'bookings-create' }), editForm: await superValidate(zod4(bookingEditSchema), { id: 'bookings-edit' }), archiveForm: await superValidate(zod4(archiveSchema), { id: 'bookings-archive' }) }; }; export const actions: Actions = { create: async ({ locals, params, request }) => { const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(bookingCreateSchema), { id: 'bookings-create' }); if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); try { await db.insert(bookings).values({ id: crypto.randomUUID(), organizationId: activeOrganizationId, clientId: form.data.clientId, roomId: form.data.roomId, serviceId: form.data.serviceId || null, startsAt: form.data.startsAt, endsAt: form.data.endsAt, status: form.data.status, notes: form.data.notes || null, updatedAt: new Date(), createdAt: new Date() }); } catch { return message(form, 'Unable to create booking.', { status: 400 }); } return message(form, 'Booking created.'); }, edit: async ({ locals, params, request }) => { const { activeOrganizationId } = await loadOrganizationContext(locals); const formData = await request.formData(); formData.set('clientId', params.id); const form = await superValidate(formData, zod4(bookingEditSchema), { id: 'bookings-edit' }); if (!form.valid) return message(form, 'Check the highlighted fields.', { status: 400 }); try { await db .update(bookings) .set({ clientId: form.data.clientId, roomId: form.data.roomId, serviceId: form.data.serviceId || null, startsAt: form.data.startsAt, endsAt: form.data.endsAt, status: form.data.status, notes: form.data.notes || null, updatedAt: new Date() }) .where( and( eq(bookings.id, form.data.id), eq(bookings.clientId, params.id), eq(bookings.organizationId, activeOrganizationId) ) ); } catch { return message(form, 'Unable to update booking.', { status: 400 }); } return message(form, 'Booking updated.'); }, archive: async ({ locals, params, request }) => { const { activeOrganizationId } = await loadOrganizationContext(locals); const form = await superValidate(await request.formData(), zod4(archiveSchema), { id: 'bookings-archive' }); if (!form.valid) return message(form, 'Booking id is required.', { status: 400 }); await db .update(bookings) .set({ archivedAt: new Date(), updatedAt: new Date() }) .where( and( eq(bookings.id, form.data.id), eq(bookings.clientId, params.id), eq(bookings.organizationId, activeOrganizationId) ) ); return message(form, 'Booking archived.'); } };