feat: add room and service management

This commit is contained in:
2026-06-05 23:25:44 +01:00
parent 631a5112f4
commit f3e4f42272
15 changed files with 1281 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import { z } from 'zod';
import { idSchema, moneyGbp, positiveInt, requiredText } from './shared.schema';
export const roomTypes = ['meeting_room', 'private_office', 'coworking_desk'] as const;
const optionalPositiveInt = (label: string) =>
z.preprocess((value) => (value === '' ? undefined : value), positiveInt(label).optional());
const typeFields = {
meeting_room: [
'sqFt',
'internalPricePerHourGbp',
'externalPricePerHourGbp',
'internalPricePerDayGbp',
'externalPricePerDayGbp',
'maxAttendees'
],
private_office: ['sqFt', 'workstations', 'pricePerMonthGbp'],
coworking_desk: [
'internalPricePerHourGbp',
'externalPricePerHourGbp',
'internalPricePerDayGbp',
'externalPricePerDayGbp'
]
} as const satisfies Record<(typeof roomTypes)[number], readonly string[]>;
const fieldLabels: Record<string, string> = {
sqFt: 'Sq ft.',
internalPricePerHourGbp: 'Internal price per hour',
externalPricePerHourGbp: 'External price per hour',
internalPricePerDayGbp: 'Internal price per day',
externalPricePerDayGbp: 'External price per day',
maxAttendees: 'Max attendees',
workstations: 'Number of workstations',
pricePerMonthGbp: 'Price per month'
};
export const roomCreateSchema = z
.object({
name: requiredText('Room name'),
type: z.enum(roomTypes).default('meeting_room'),
sqFt: optionalPositiveInt('Sq ft.'),
internalPricePerHourGbp: moneyGbp,
externalPricePerHourGbp: moneyGbp,
internalPricePerDayGbp: moneyGbp,
externalPricePerDayGbp: moneyGbp,
maxAttendees: optionalPositiveInt('Max attendees'),
workstations: optionalPositiveInt('Number of workstations'),
pricePerMonthGbp: moneyGbp
})
.superRefine((data, ctx) => {
for (const field of typeFields[data.type]) {
const value = data[field as keyof typeof data];
if (value === undefined || value === null || value === '') {
ctx.addIssue({
code: 'custom',
path: [field],
message: `${fieldLabels[field]} is required.`
});
}
}
});
export const roomEditSchema = roomCreateSchema.extend(idSchema.shape);
+12
View File
@@ -0,0 +1,12 @@
import { z } from 'zod';
import { idSchema, moneyGbp, optionalText, requiredText } from './shared.schema';
export const serviceCreateSchema = z.object({
name: requiredText('Service name'),
category: optionalText(120),
unit: requiredText('Unit', 80).default('each'),
priceGbp: moneyGbp,
description: optionalText(1000)
});
export const serviceEditSchema = serviceCreateSchema.extend(idSchema.shape);