feat: add schedule column labels, office hours enforcement, and appointment move fix

- Schedule columns default to labels A–F (localStorage, per-browser, click to rename)
- Settings → Advanced → Office Hours: configure Doctors (A-C) and Hygienists (D-F) AM/PM hours per weekday
- Gray out schedule slots outside office hours; override dialog for manual exceptions
- Override Office Hours toggle: select specific dates where all slots are open
- Fix appointment move: send only real DB fields to avoid Zod strict-mode rejection of computed fields (hasProcedures, hasClaimWithNumber)
- Fix backend PUT /appointments: safe error logging to prevent Prisma error crashing Node inspect
- Add OfficeHours Prisma model and GET/PUT /api/office-hours route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-05 09:15:18 -04:00
parent 70ffd8398b
commit 2312ad66ca
465 changed files with 11834 additions and 1461 deletions

View File

@@ -0,0 +1,25 @@
import * as z from 'zod';
export const AiSettingsAggregateResultSchema = z.object({ _count: z.object({
id: z.number(),
userId: z.number(),
apiKey: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
apiKey: z.string().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
apiKey: z.string().nullable()
}).nullable().optional()});

View File

@@ -0,0 +1,2 @@
import * as z from 'zod';
export const AiSettingsCountResultSchema = z.number();

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const AiSettingsCreateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const AiSettingsCreateResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
user: z.unknown()
});

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const AiSettingsDeleteManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const AiSettingsDeleteResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const AiSettingsFindFirstResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,17 @@
import * as z from 'zod';
export const AiSettingsFindManyResultSchema = z.object({
data: z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
user: z.unknown()
})),
pagination: z.object({
page: z.number().int().min(1),
pageSize: z.number().int().min(1),
total: z.number().int().min(0),
totalPages: z.number().int().min(0),
hasNext: z.boolean(),
hasPrev: z.boolean()
})
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const AiSettingsFindUniqueResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,30 @@
import * as z from 'zod';
export const AiSettingsGroupByResultSchema = z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
_count: z.object({
id: z.number(),
userId: z.number(),
apiKey: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
apiKey: z.string().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
apiKey: z.string().nullable()
}).nullable().optional()
}));

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const AiSettingsUpdateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const AiSettingsUpdateResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const AiSettingsUpsertResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
apiKey: z.string(),
user: z.unknown()
});

View File

@@ -0,0 +1,23 @@
import * as z from 'zod';
export const OfficeHoursAggregateResultSchema = z.object({ _count: z.object({
id: z.number(),
userId: z.number(),
data: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable()
}).nullable().optional()});

View File

@@ -0,0 +1,2 @@
import * as z from 'zod';
export const OfficeHoursCountResultSchema = z.number();

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const OfficeHoursCreateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const OfficeHoursCreateResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
user: z.unknown()
});

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const OfficeHoursDeleteManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const OfficeHoursDeleteResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
user: z.unknown()
}));

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const OfficeHoursFindFirstResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
user: z.unknown()
}));

View File

@@ -0,0 +1,17 @@
import * as z from 'zod';
export const OfficeHoursFindManyResultSchema = z.object({
data: z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
user: z.unknown()
})),
pagination: z.object({
page: z.number().int().min(1),
pageSize: z.number().int().min(1),
total: z.number().int().min(0),
totalPages: z.number().int().min(0),
hasNext: z.boolean(),
hasPrev: z.boolean()
})
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const OfficeHoursFindUniqueResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
user: z.unknown()
}));

View File

@@ -0,0 +1,28 @@
import * as z from 'zod';
export const OfficeHoursGroupByResultSchema = z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
_count: z.object({
id: z.number(),
userId: z.number(),
data: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable()
}).nullable().optional()
}));

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const OfficeHoursUpdateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const OfficeHoursUpdateResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
user: z.unknown()
}));

View File

@@ -0,0 +1,7 @@
import * as z from 'zod';
export const OfficeHoursUpsertResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
data: z.unknown(),
user: z.unknown()
});

View File

@@ -0,0 +1,35 @@
import * as z from 'zod';
export const TwilioSettingsAggregateResultSchema = z.object({ _count: z.object({
id: z.number(),
userId: z.number(),
accountSid: z.number(),
authToken: z.number(),
phoneNumber: z.number(),
greetingMessage: z.number(),
templates: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
accountSid: z.string().nullable(),
authToken: z.string().nullable(),
phoneNumber: z.string().nullable(),
greetingMessage: z.string().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
accountSid: z.string().nullable(),
authToken: z.string().nullable(),
phoneNumber: z.string().nullable(),
greetingMessage: z.string().nullable()
}).nullable().optional()});

View File

@@ -0,0 +1,2 @@
import * as z from 'zod';
export const TwilioSettingsCountResultSchema = z.number();

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const TwilioSettingsCreateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,11 @@
import * as z from 'zod';
export const TwilioSettingsCreateResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string().optional(),
templates: z.unknown().optional(),
user: z.unknown()
});

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const TwilioSettingsDeleteManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,11 @@
import * as z from 'zod';
export const TwilioSettingsDeleteResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string().optional(),
templates: z.unknown().optional(),
user: z.unknown()
}));

View File

@@ -0,0 +1,11 @@
import * as z from 'zod';
export const TwilioSettingsFindFirstResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string().optional(),
templates: z.unknown().optional(),
user: z.unknown()
}));

View File

@@ -0,0 +1,21 @@
import * as z from 'zod';
export const TwilioSettingsFindManyResultSchema = z.object({
data: z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string().optional(),
templates: z.unknown().optional(),
user: z.unknown()
})),
pagination: z.object({
page: z.number().int().min(1),
pageSize: z.number().int().min(1),
total: z.number().int().min(0),
totalPages: z.number().int().min(0),
hasNext: z.boolean(),
hasPrev: z.boolean()
})
});

View File

@@ -0,0 +1,11 @@
import * as z from 'zod';
export const TwilioSettingsFindUniqueResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string().optional(),
templates: z.unknown().optional(),
user: z.unknown()
}));

View File

@@ -0,0 +1,44 @@
import * as z from 'zod';
export const TwilioSettingsGroupByResultSchema = z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string(),
templates: z.unknown(),
_count: z.object({
id: z.number(),
userId: z.number(),
accountSid: z.number(),
authToken: z.number(),
phoneNumber: z.number(),
greetingMessage: z.number(),
templates: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
accountSid: z.string().nullable(),
authToken: z.string().nullable(),
phoneNumber: z.string().nullable(),
greetingMessage: z.string().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
accountSid: z.string().nullable(),
authToken: z.string().nullable(),
phoneNumber: z.string().nullable(),
greetingMessage: z.string().nullable()
}).nullable().optional()
}));

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const TwilioSettingsUpdateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,11 @@
import * as z from 'zod';
export const TwilioSettingsUpdateResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string().optional(),
templates: z.unknown().optional(),
user: z.unknown()
}));

View File

@@ -0,0 +1,11 @@
import * as z from 'zod';
export const TwilioSettingsUpsertResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
accountSid: z.string(),
authToken: z.string(),
phoneNumber: z.string(),
greetingMessage: z.string().optional(),
templates: z.unknown().optional(),
user: z.unknown()
});

View File

@@ -17,7 +17,10 @@ export const UserAggregateResultSchema = z.object({ _count: z.object({
notifications: z.number(),
cloudFolders: z.number(),
cloudFiles: z.number(),
communications: z.number()
communications: z.number(),
twilioSettings: z.number(),
aiSettings: z.number(),
officeHours: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable()

View File

@@ -17,5 +17,8 @@ export const UserCreateResultSchema = z.object({
notifications: z.array(z.unknown()),
cloudFolders: z.array(z.unknown()),
cloudFiles: z.array(z.unknown()),
communications: z.array(z.unknown())
communications: z.array(z.unknown()),
twilioSettings: z.unknown().optional(),
aiSettings: z.unknown().optional(),
officeHours: z.unknown().optional()
});

View File

@@ -17,5 +17,8 @@ export const UserDeleteResultSchema = z.nullable(z.object({
notifications: z.array(z.unknown()),
cloudFolders: z.array(z.unknown()),
cloudFiles: z.array(z.unknown()),
communications: z.array(z.unknown())
communications: z.array(z.unknown()),
twilioSettings: z.unknown().optional(),
aiSettings: z.unknown().optional(),
officeHours: z.unknown().optional()
}));

View File

@@ -17,5 +17,8 @@ export const UserFindFirstResultSchema = z.nullable(z.object({
notifications: z.array(z.unknown()),
cloudFolders: z.array(z.unknown()),
cloudFiles: z.array(z.unknown()),
communications: z.array(z.unknown())
communications: z.array(z.unknown()),
twilioSettings: z.unknown().optional(),
aiSettings: z.unknown().optional(),
officeHours: z.unknown().optional()
}));

View File

@@ -18,7 +18,10 @@ export const UserFindManyResultSchema = z.object({
notifications: z.array(z.unknown()),
cloudFolders: z.array(z.unknown()),
cloudFiles: z.array(z.unknown()),
communications: z.array(z.unknown())
communications: z.array(z.unknown()),
twilioSettings: z.unknown().optional(),
aiSettings: z.unknown().optional(),
officeHours: z.unknown().optional()
})),
pagination: z.object({
page: z.number().int().min(1),

View File

@@ -17,5 +17,8 @@ export const UserFindUniqueResultSchema = z.nullable(z.object({
notifications: z.array(z.unknown()),
cloudFolders: z.array(z.unknown()),
cloudFiles: z.array(z.unknown()),
communications: z.array(z.unknown())
communications: z.array(z.unknown()),
twilioSettings: z.unknown().optional(),
aiSettings: z.unknown().optional(),
officeHours: z.unknown().optional()
}));

View File

@@ -23,7 +23,10 @@ export const UserGroupByResultSchema = z.array(z.object({
notifications: z.number(),
cloudFolders: z.number(),
cloudFiles: z.number(),
communications: z.number()
communications: z.number(),
twilioSettings: z.number(),
aiSettings: z.number(),
officeHours: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable()

View File

@@ -17,5 +17,8 @@ export const UserUpdateResultSchema = z.nullable(z.object({
notifications: z.array(z.unknown()),
cloudFolders: z.array(z.unknown()),
cloudFiles: z.array(z.unknown()),
communications: z.array(z.unknown())
communications: z.array(z.unknown()),
twilioSettings: z.unknown().optional(),
aiSettings: z.unknown().optional(),
officeHours: z.unknown().optional()
}));

View File

@@ -17,5 +17,8 @@ export const UserUpsertResultSchema = z.object({
notifications: z.array(z.unknown()),
cloudFolders: z.array(z.unknown()),
cloudFiles: z.array(z.unknown()),
communications: z.array(z.unknown())
communications: z.array(z.unknown()),
twilioSettings: z.unknown().optional(),
aiSettings: z.unknown().optional(),
officeHours: z.unknown().optional()
});

View File

@@ -310,3 +310,42 @@ export { PatientDocumentDeleteManyResultSchema } from './PatientDocumentDeleteMa
export { PatientDocumentAggregateResultSchema } from './PatientDocumentAggregateResult.schema';
export { PatientDocumentGroupByResultSchema } from './PatientDocumentGroupByResult.schema';
export { PatientDocumentCountResultSchema } from './PatientDocumentCountResult.schema';
export { TwilioSettingsFindUniqueResultSchema } from './TwilioSettingsFindUniqueResult.schema';
export { TwilioSettingsFindFirstResultSchema } from './TwilioSettingsFindFirstResult.schema';
export { TwilioSettingsFindManyResultSchema } from './TwilioSettingsFindManyResult.schema';
export { TwilioSettingsCreateResultSchema } from './TwilioSettingsCreateResult.schema';
export { TwilioSettingsCreateManyResultSchema } from './TwilioSettingsCreateManyResult.schema';
export { TwilioSettingsUpdateResultSchema } from './TwilioSettingsUpdateResult.schema';
export { TwilioSettingsUpdateManyResultSchema } from './TwilioSettingsUpdateManyResult.schema';
export { TwilioSettingsUpsertResultSchema } from './TwilioSettingsUpsertResult.schema';
export { TwilioSettingsDeleteResultSchema } from './TwilioSettingsDeleteResult.schema';
export { TwilioSettingsDeleteManyResultSchema } from './TwilioSettingsDeleteManyResult.schema';
export { TwilioSettingsAggregateResultSchema } from './TwilioSettingsAggregateResult.schema';
export { TwilioSettingsGroupByResultSchema } from './TwilioSettingsGroupByResult.schema';
export { TwilioSettingsCountResultSchema } from './TwilioSettingsCountResult.schema';
export { AiSettingsFindUniqueResultSchema } from './AiSettingsFindUniqueResult.schema';
export { AiSettingsFindFirstResultSchema } from './AiSettingsFindFirstResult.schema';
export { AiSettingsFindManyResultSchema } from './AiSettingsFindManyResult.schema';
export { AiSettingsCreateResultSchema } from './AiSettingsCreateResult.schema';
export { AiSettingsCreateManyResultSchema } from './AiSettingsCreateManyResult.schema';
export { AiSettingsUpdateResultSchema } from './AiSettingsUpdateResult.schema';
export { AiSettingsUpdateManyResultSchema } from './AiSettingsUpdateManyResult.schema';
export { AiSettingsUpsertResultSchema } from './AiSettingsUpsertResult.schema';
export { AiSettingsDeleteResultSchema } from './AiSettingsDeleteResult.schema';
export { AiSettingsDeleteManyResultSchema } from './AiSettingsDeleteManyResult.schema';
export { AiSettingsAggregateResultSchema } from './AiSettingsAggregateResult.schema';
export { AiSettingsGroupByResultSchema } from './AiSettingsGroupByResult.schema';
export { AiSettingsCountResultSchema } from './AiSettingsCountResult.schema';
export { OfficeHoursFindUniqueResultSchema } from './OfficeHoursFindUniqueResult.schema';
export { OfficeHoursFindFirstResultSchema } from './OfficeHoursFindFirstResult.schema';
export { OfficeHoursFindManyResultSchema } from './OfficeHoursFindManyResult.schema';
export { OfficeHoursCreateResultSchema } from './OfficeHoursCreateResult.schema';
export { OfficeHoursCreateManyResultSchema } from './OfficeHoursCreateManyResult.schema';
export { OfficeHoursUpdateResultSchema } from './OfficeHoursUpdateResult.schema';
export { OfficeHoursUpdateManyResultSchema } from './OfficeHoursUpdateManyResult.schema';
export { OfficeHoursUpsertResultSchema } from './OfficeHoursUpsertResult.schema';
export { OfficeHoursDeleteResultSchema } from './OfficeHoursDeleteResult.schema';
export { OfficeHoursDeleteManyResultSchema } from './OfficeHoursDeleteManyResult.schema';
export { OfficeHoursAggregateResultSchema } from './OfficeHoursAggregateResult.schema';
export { OfficeHoursGroupByResultSchema } from './OfficeHoursGroupByResult.schema';
export { OfficeHoursCountResultSchema } from './OfficeHoursCountResult.schema';