feat: show real attendee count with guests in admin, expandable guest details, fix CSV export
This commit is contained in:
@@ -474,27 +474,49 @@ export const appRouter = {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const [totalResult, todayResult, artFormResult, typeResult, giftResult] =
|
||||
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),
|
||||
db.select({ total: sum(registration.giftAmount) }).from(registration),
|
||||
]);
|
||||
const [
|
||||
totalResult,
|
||||
todayResult,
|
||||
artFormResult,
|
||||
typeResult,
|
||||
giftResult,
|
||||
watcherGuestRows,
|
||||
] = 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),
|
||||
db.select({ total: sum(registration.giftAmount) }).from(registration),
|
||||
// Fetch guests column for all watcher registrations to count total guests
|
||||
db
|
||||
.select({ guests: registration.guests })
|
||||
.from(registration)
|
||||
.where(
|
||||
and(
|
||||
eq(registration.registrationType, "watcher"),
|
||||
isNull(registration.cancelledAt),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
// Sum up all guest counts across all watcher registrations
|
||||
const totalGuestCount = watcherGuestRows.reduce((sum, r) => {
|
||||
const guests = parseGuestsJson(r.guests);
|
||||
return sum + guests.length;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
total: totalResult[0]?.count ?? 0,
|
||||
@@ -508,6 +530,7 @@ export const appRouter = {
|
||||
count: r.count,
|
||||
})),
|
||||
totalGiftRevenue: giftResult[0]?.total ?? 0,
|
||||
totalGuestCount,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -517,6 +540,9 @@ export const appRouter = {
|
||||
.from(registration)
|
||||
.orderBy(desc(registration.createdAt));
|
||||
|
||||
const escapeCell = (val: string) => `"${val.replace(/"/g, '""')}"`;
|
||||
|
||||
// Main registration headers
|
||||
const headers = [
|
||||
"ID",
|
||||
"First Name",
|
||||
@@ -527,49 +553,97 @@ export const appRouter = {
|
||||
"Art Form",
|
||||
"Experience",
|
||||
"Is Over 16",
|
||||
"Drink Card Value",
|
||||
"Gift Amount",
|
||||
"Drink Card Value (EUR)",
|
||||
"Gift Amount (cents)",
|
||||
"Guest Count",
|
||||
"Guests",
|
||||
"Guest 1 First Name",
|
||||
"Guest 1 Last Name",
|
||||
"Guest 1 Email",
|
||||
"Guest 1 Phone",
|
||||
"Guest 2 First Name",
|
||||
"Guest 2 Last Name",
|
||||
"Guest 2 Email",
|
||||
"Guest 2 Phone",
|
||||
"Guest 3 First Name",
|
||||
"Guest 3 Last Name",
|
||||
"Guest 3 Email",
|
||||
"Guest 3 Phone",
|
||||
"Guest 4 First Name",
|
||||
"Guest 4 Last Name",
|
||||
"Guest 4 Email",
|
||||
"Guest 4 Phone",
|
||||
"Guest 5 First Name",
|
||||
"Guest 5 Last Name",
|
||||
"Guest 5 Email",
|
||||
"Guest 5 Phone",
|
||||
"Guest 6 First Name",
|
||||
"Guest 6 Last Name",
|
||||
"Guest 6 Email",
|
||||
"Guest 6 Phone",
|
||||
"Guest 7 First Name",
|
||||
"Guest 7 Last Name",
|
||||
"Guest 7 Email",
|
||||
"Guest 7 Phone",
|
||||
"Guest 8 First Name",
|
||||
"Guest 8 Last Name",
|
||||
"Guest 8 Email",
|
||||
"Guest 8 Phone",
|
||||
"Guest 9 First Name",
|
||||
"Guest 9 Last Name",
|
||||
"Guest 9 Email",
|
||||
"Guest 9 Phone",
|
||||
"Payment Status",
|
||||
"Paid At",
|
||||
"Extra Questions",
|
||||
"Cancelled At",
|
||||
"Created At",
|
||||
];
|
||||
|
||||
const MAX_GUESTS = 9;
|
||||
|
||||
const rows = data.map((r) => {
|
||||
const guests = parseGuestsJson(r.guests);
|
||||
const guestSummary = guests
|
||||
.map(
|
||||
(g) =>
|
||||
`${g.firstName} ${g.lastName}${g.email ? ` <${g.email}>` : ""}${g.phone ? ` (${g.phone})` : ""}`,
|
||||
)
|
||||
.join(" | ");
|
||||
|
||||
// Build guest columns (up to 9 guests, 4 fields each)
|
||||
const guestCols: string[] = [];
|
||||
for (let i = 0; i < MAX_GUESTS; i++) {
|
||||
const g = guests[i];
|
||||
guestCols.push(g?.firstName ?? "");
|
||||
guestCols.push(g?.lastName ?? "");
|
||||
guestCols.push(g?.email ?? "");
|
||||
guestCols.push(g?.phone ?? "");
|
||||
}
|
||||
|
||||
return [
|
||||
r.id,
|
||||
r.firstName,
|
||||
r.lastName,
|
||||
r.email,
|
||||
r.phone || "",
|
||||
r.registrationType,
|
||||
r.registrationType ?? "",
|
||||
r.artForm || "",
|
||||
r.experience || "",
|
||||
r.isOver16 ? "Yes" : "No",
|
||||
String(r.drinkCardValue ?? 0),
|
||||
String(r.giftAmount ?? 0),
|
||||
String(guests.length),
|
||||
guestSummary,
|
||||
r.paymentStatus === "paid" ? "Paid" : "Pending",
|
||||
...guestCols,
|
||||
r.paymentStatus === "paid"
|
||||
? "Paid"
|
||||
: r.paymentStatus === "extra_payment_pending"
|
||||
? "Extra Payment Pending"
|
||||
: "Pending",
|
||||
r.paidAt ? r.paidAt.toISOString() : "",
|
||||
r.extraQuestions || "",
|
||||
r.cancelledAt ? r.cancelledAt.toISOString() : "",
|
||||
r.createdAt.toISOString(),
|
||||
];
|
||||
});
|
||||
|
||||
const csvContent = [
|
||||
headers.join(","),
|
||||
headers.map(escapeCell).join(","),
|
||||
...rows.map((row) =>
|
||||
row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(","),
|
||||
row.map((cell) => escapeCell(String(cell))).join(","),
|
||||
),
|
||||
].join("\n");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user