feat:multiple bezoekers

This commit is contained in:
2026-03-03 10:36:08 +01:00
parent 1210b2e13e
commit f6c2bad9df
12 changed files with 1891 additions and 574 deletions

View File

@@ -12,19 +12,34 @@ import {
} from "../email";
import { adminProcedure, protectedProcedure, publicProcedure } from "../index";
const registrationTypeSchema = z.enum(["performer", "watcher"]);
const guestSchema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email().optional().or(z.literal("")),
phone: z.string().optional(),
});
const submitRegistrationSchema = z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(),
phone: z.string().optional(),
wantsToPerform: z.boolean().default(false),
registrationType: registrationTypeSchema.default("watcher"),
// Performer-specific
artForm: z.string().optional(),
experience: z.string().optional(),
isOver16: z.boolean().optional(),
// Watcher-specific: drinkCardValue is computed server-side, guests are named
guests: z.array(guestSchema).max(9).optional(),
// Shared
extraQuestions: z.string().optional(),
});
const getRegistrationsSchema = z.object({
search: z.string().optional(),
registrationType: registrationTypeSchema.optional(),
artForm: z.string().optional(),
fromDate: z.string().datetime().optional(),
toDate: z.string().datetime().optional(),
@@ -48,15 +63,22 @@ export const appRouter = {
.input(submitRegistrationSchema)
.handler(async ({ input }) => {
const managementToken = randomUUID();
const isPerformer = input.registrationType === "performer";
const guests = isPerformer ? [] : (input.guests ?? []);
// €5 for primary registrant + €2 per extra guest
const drinkCardValue = isPerformer ? 0 : 5 + guests.length * 2;
await db.insert(registration).values({
id: randomUUID(),
firstName: input.firstName,
lastName: input.lastName,
email: input.email,
phone: input.phone || null,
wantsToPerform: input.wantsToPerform,
artForm: input.artForm || null,
experience: input.experience || null,
registrationType: input.registrationType,
artForm: isPerformer ? input.artForm || null : null,
experience: isPerformer ? input.experience || null : null,
isOver16: isPerformer ? (input.isOver16 ?? false) : false,
drinkCardValue,
guests: guests.length > 0 ? JSON.stringify(guests) : null,
extraQuestions: input.extraQuestions || null,
managementToken,
});
@@ -65,7 +87,7 @@ export const appRouter = {
to: input.email,
firstName: input.firstName,
managementToken,
wantsToPerform: input.wantsToPerform,
wantsToPerform: isPerformer,
artForm: input.artForm,
}).catch((err) =>
console.error("Failed to send confirmation email:", err),
@@ -98,9 +120,11 @@ export const appRouter = {
lastName: z.string().min(1),
email: z.string().email(),
phone: z.string().optional(),
wantsToPerform: z.boolean().default(false),
registrationType: registrationTypeSchema.default("watcher"),
artForm: z.string().optional(),
experience: z.string().optional(),
isOver16: z.boolean().optional(),
guests: z.array(guestSchema).max(9).optional(),
extraQuestions: z.string().optional(),
}),
)
@@ -119,6 +143,9 @@ export const appRouter = {
const row = rows[0];
if (!row) throw new Error("Inschrijving niet gevonden of al geannuleerd");
const isPerformer = input.registrationType === "performer";
const guests = isPerformer ? [] : (input.guests ?? []);
const drinkCardValue = isPerformer ? 0 : 5 + guests.length * 2;
await db
.update(registration)
.set({
@@ -126,9 +153,12 @@ export const appRouter = {
lastName: input.lastName,
email: input.email,
phone: input.phone || null,
wantsToPerform: input.wantsToPerform,
artForm: input.artForm || null,
experience: input.experience || null,
registrationType: input.registrationType,
artForm: isPerformer ? input.artForm || null : null,
experience: isPerformer ? input.experience || null : null,
isOver16: isPerformer ? (input.isOver16 ?? false) : false,
drinkCardValue,
guests: guests.length > 0 ? JSON.stringify(guests) : null,
extraQuestions: input.extraQuestions || null,
})
.where(eq(registration.managementToken, input.token));
@@ -137,7 +167,7 @@ export const appRouter = {
to: input.email,
firstName: input.firstName,
managementToken: input.token,
wantsToPerform: input.wantsToPerform,
wantsToPerform: isPerformer,
artForm: input.artForm,
}).catch((err) => console.error("Failed to send update email:", err));
@@ -192,6 +222,12 @@ export const appRouter = {
);
}
if (input.registrationType) {
conditions.push(
eq(registration.registrationType, input.registrationType),
);
}
if (input.artForm) {
conditions.push(eq(registration.artForm, input.artForm));
}
@@ -235,20 +271,29 @@ export const appRouter = {
const today = new Date();
today.setHours(0, 0, 0, 0);
const [totalResult, todayResult, artFormResult] = await Promise.all([
db.select({ count: count() }).from(registration),
db
.select({ count: count() })
.from(registration)
.where(gte(registration.createdAt, today)),
db
.select({
artForm: registration.artForm,
count: count(),
})
.from(registration)
.groupBy(registration.artForm),
]);
const [totalResult, todayResult, artFormResult, typeResult] =
await Promise.all([
db.select({ count: count() }).from(registration),
db
.select({ count: count() })
.from(registration)
.where(gte(registration.createdAt, today)),
db
.select({
artForm: registration.artForm,
count: count(),
})
.from(registration)
.where(eq(registration.registrationType, "performer"))
.groupBy(registration.artForm),
db
.select({
registrationType: registration.registrationType,
count: count(),
})
.from(registration)
.groupBy(registration.registrationType),
]);
return {
total: totalResult[0]?.count ?? 0,
@@ -257,6 +302,10 @@ export const appRouter = {
artForm: r.artForm,
count: r.count,
})),
byType: typeResult.map((r) => ({
registrationType: r.registrationType,
count: r.count,
})),
};
}),
@@ -272,24 +321,46 @@ export const appRouter = {
"Last Name",
"Email",
"Phone",
"Wants To Perform",
"Type",
"Art Form",
"Experience",
"Is Over 16",
"Drink Card Value",
"Guest Count",
"Guests",
"Extra Questions",
"Created At",
];
const rows = data.map((r) => [
r.id,
r.firstName,
r.lastName,
r.email,
r.phone || "",
r.wantsToPerform ? "Yes" : "No",
r.artForm || "",
r.experience || "",
r.extraQuestions || "",
r.createdAt.toISOString(),
]);
const rows = data.map((r) => {
const guests: Array<{
firstName: string;
lastName: string;
email?: string;
phone?: string;
}> = r.guests ? JSON.parse(r.guests) : [];
const guestSummary = guests
.map(
(g) =>
`${g.firstName} ${g.lastName}${g.email ? ` <${g.email}>` : ""}${g.phone ? ` (${g.phone})` : ""}`,
)
.join(" | ");
return [
r.id,
r.firstName,
r.lastName,
r.email,
r.phone || "",
r.registrationType,
r.artForm || "",
r.experience || "",
r.isOver16 ? "Yes" : "No",
String(r.drinkCardValue ?? 0),
String(guests.length),
guestSummary,
r.extraQuestions || "",
r.createdAt.toISOString(),
];
});
const csvContent = [
headers.join(","),
@@ -321,16 +392,17 @@ export const appRouter = {
.limit(1);
if (existingRequest.length > 0) {
if (existingRequest[0].status === "pending") {
const existing = existingRequest[0]!;
if (existing.status === "pending") {
return { success: false, message: "Je hebt al een aanvraag openstaan" };
}
if (existingRequest[0].status === "approved") {
if (existing.status === "approved") {
return {
success: false,
message: "Je aanvraag is al goedgekeurd, log opnieuw in",
};
}
if (existingRequest[0].status === "rejected") {
if (existing.status === "rejected") {
// Allow re-requesting if previously rejected
await db
.update(adminRequest)
@@ -387,7 +459,8 @@ export const appRouter = {
throw new Error("Aanvraag niet gevonden");
}
if (request[0].status !== "pending") {
const req = request[0]!;
if (req.status !== "pending") {
throw new Error("Deze aanvraag is al behandeld");
}
@@ -405,7 +478,7 @@ export const appRouter = {
await db
.update(user)
.set({ role: "admin" })
.where(eq(user.id, request[0].userId));
.where(eq(user.id, req.userId));
return { success: true, message: "Admin toegang goedgekeurd" };
}),
@@ -423,7 +496,8 @@ export const appRouter = {
throw new Error("Aanvraag niet gevonden");
}
if (request[0].status !== "pending") {
const req = request[0]!;
if (req.status !== "pending") {
throw new Error("Deze aanvraag is al behandeld");
}