feat: add client workspace management
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import { booleanString, idSchema, optionalText, requiredText } from './shared.schema';
|
||||
|
||||
export const addressCreateSchema = z.object({
|
||||
clientId: requiredText('Client'),
|
||||
label: requiredText('Label', 80),
|
||||
line1: requiredText('Address line 1'),
|
||||
line2: optionalText(160),
|
||||
city: requiredText('City', 100),
|
||||
region: optionalText(100),
|
||||
postcode: requiredText('Postcode', 24),
|
||||
country: requiredText('Country', 80).default('United Kingdom'),
|
||||
isPrimary: booleanString
|
||||
});
|
||||
|
||||
export const addressEditSchema = addressCreateSchema.extend(idSchema.shape);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
import { idSchema, optionalText, requiredText } from './shared.schema';
|
||||
|
||||
export const bookingCreateSchema = z.object({
|
||||
clientId: requiredText('Client'),
|
||||
roomId: requiredText('Room'),
|
||||
serviceId: optionalText(80),
|
||||
startsAt: requiredText('Start time', 40),
|
||||
endsAt: requiredText('End time', 40),
|
||||
status: z.enum(['booked', 'confirmed', 'completed', 'cancelled']).default('booked'),
|
||||
notes: optionalText(1000)
|
||||
});
|
||||
|
||||
export const bookingEditSchema = bookingCreateSchema.extend(idSchema.shape);
|
||||
@@ -0,0 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
import { idSchema, optionalEmail, optionalText, optionalUrl, requiredText } from './shared.schema';
|
||||
|
||||
export const clientCreateSchema = z.object({
|
||||
name: requiredText('Client name'),
|
||||
type: z.enum(['business', 'sport', 'individual']).default('business'),
|
||||
status: z.enum(['active', 'prospect', 'paused']).default('active'),
|
||||
website: optionalUrl,
|
||||
notes: optionalText(1000)
|
||||
});
|
||||
|
||||
export const clientEditSchema = clientCreateSchema.extend(idSchema.shape);
|
||||
|
||||
export const clientOnboardingCreateSchema = clientCreateSchema.extend({
|
||||
primaryContactName: requiredText('Primary contact name'),
|
||||
primaryContactRole: optionalText(120),
|
||||
primaryContactEmail: optionalEmail,
|
||||
primaryContactPhone: optionalText(80),
|
||||
primaryContactNotes: optionalText(1000),
|
||||
primaryAddressLabel: requiredText('Address label', 80).default('Primary'),
|
||||
primaryAddressLine1: requiredText('Address line 1'),
|
||||
primaryAddressLine2: optionalText(160),
|
||||
primaryAddressCity: requiredText('City', 100),
|
||||
primaryAddressRegion: optionalText(100),
|
||||
primaryAddressPostcode: requiredText('Postcode', 24),
|
||||
primaryAddressCountry: requiredText('Country', 80).default('United Kingdom')
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
booleanString,
|
||||
idSchema,
|
||||
optionalEmail,
|
||||
optionalText,
|
||||
requiredText
|
||||
} from './shared.schema';
|
||||
|
||||
export const contactCreateSchema = z.object({
|
||||
clientId: requiredText('Client'),
|
||||
name: requiredText('Contact name'),
|
||||
role: optionalText(120),
|
||||
email: optionalEmail,
|
||||
phone: optionalText(80),
|
||||
isPrimary: booleanString,
|
||||
notes: optionalText(1000)
|
||||
});
|
||||
|
||||
export const contactEditSchema = contactCreateSchema.extend(idSchema.shape);
|
||||
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
import { idSchema, moneyGbp, optionalText, requiredText } from './shared.schema';
|
||||
|
||||
export const contractCreateSchema = z.object({
|
||||
clientId: requiredText('Client'),
|
||||
title: requiredText('Contract title'),
|
||||
startDate: requiredText('Start date', 40),
|
||||
endDate: optionalText(40),
|
||||
status: z.enum(['draft', 'active', 'expired', 'cancelled']).default('draft'),
|
||||
valueGbp: moneyGbp,
|
||||
notes: optionalText(1000)
|
||||
});
|
||||
|
||||
export const contractEditSchema = contractCreateSchema.extend(idSchema.shape);
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import { idSchema, moneyGbp, optionalText, requiredText } from './shared.schema';
|
||||
|
||||
export const invoiceCreateSchema = z.object({
|
||||
clientId: requiredText('Client'),
|
||||
invoiceNumber: requiredText('Invoice number', 80),
|
||||
issueDate: requiredText('Issue date', 40),
|
||||
dueDate: requiredText('Due date', 40),
|
||||
status: z.enum(['draft', 'sent', 'paid', 'overdue', 'void']).default('draft'),
|
||||
subtotalGbp: moneyGbp,
|
||||
taxGbp: moneyGbp,
|
||||
totalGbp: moneyGbp,
|
||||
notes: optionalText(1000)
|
||||
});
|
||||
|
||||
export const invoiceEditSchema = invoiceCreateSchema.extend(idSchema.shape);
|
||||
@@ -0,0 +1,39 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const idSchema = z.object({
|
||||
id: z.string().uuid()
|
||||
});
|
||||
|
||||
export const archiveSchema = idSchema;
|
||||
|
||||
export const requiredText = (label: string, max = 160) =>
|
||||
z.string().trim().min(1, `${label} is required.`).max(max, `${label} is too long.`);
|
||||
|
||||
export const optionalText = (max = 500) => z.string().trim().max(max).optional().default('');
|
||||
|
||||
export const optionalEmail = z
|
||||
.string()
|
||||
.trim()
|
||||
.email('Enter a valid email address.')
|
||||
.or(z.literal(''))
|
||||
.default('');
|
||||
|
||||
export const optionalUrl = z
|
||||
.string()
|
||||
.trim()
|
||||
.url('Enter a valid URL.')
|
||||
.or(z.literal(''))
|
||||
.default('');
|
||||
|
||||
export const moneyGbp = z.coerce
|
||||
.number({ error: 'Enter an amount in GBP.' })
|
||||
.min(0, 'Amount cannot be negative.')
|
||||
.default(0);
|
||||
|
||||
export const positiveInt = (label: string) =>
|
||||
z.coerce
|
||||
.number({ error: `${label} is required.` })
|
||||
.int()
|
||||
.min(1, `${label} must be at least 1.`);
|
||||
|
||||
export const booleanString = z.enum(['true', 'false']).default('false');
|
||||
Reference in New Issue
Block a user