Files
anouma/lib/booking/adminCalendar.ts
T
maro 5d83c0dc1e Add calendar-based booking system: accounts, availability, admin calendar
- Customer accounts (separate auth collection) with /konto area
- Availability + AvailabilityOverrides collections driving real slot calculation
- BookingRequests with race-safe confirmation (Postgres advisory lock + transaction)
- Booking emails (request received, admin notify, confirmed, rejected, alternative proposed/accepted, cancelled)
- Confirmed online bookings auto-create a private linked video-call Event
- Custom Payload admin calendar view (month grid + day schedule)
- Wired booking widget into offer pages and /termin-buchen
2026-08-25 16:52:34 +02:00

82 lines
2.5 KiB
TypeScript

import type { Payload } from "payload";
import type { BookingRequest, Customer, Event, Offer } from "@/payload-types";
export type AdminCalendarItem = {
id: string;
kind: "booking" | "event";
title: string;
subtitle?: string;
date: string;
startTime: string;
endTime: string;
status: string; // booking status, or "public" for a standalone Event
appointmentType?: "onsite" | "online";
href: string;
};
/**
* Combines private BookingRequests with standalone public Events (excluding
* the auto-generated Events already represented by a confirmed online
* booking, so Anna never sees the same appointment listed twice) into one
* unified calendar feed for the admin view.
*/
export async function getAdminCalendarItems(
payload: Payload,
range: { from: Date; to: Date },
): Promise<AdminCalendarItem[]> {
const fromISO = range.from.toISOString();
const toISO = range.to.toISOString();
const [{ docs: bookings }, { docs: events }] = await Promise.all([
payload.find({
collection: "booking-requests",
where: { and: [{ date: { greater_than_equal: fromISO } }, { date: { less_than_equal: toISO } }] },
depth: 2,
limit: 500,
}),
payload.find({
collection: "events",
where: {
and: [
{ date: { greater_than_equal: fromISO } },
{ date: { less_than_equal: toISO } },
{ isPrivateBooking: { not_equals: true } },
],
},
depth: 0,
limit: 500,
}),
]);
const bookingItems: AdminCalendarItem[] = (bookings as BookingRequest[]).map((b) => {
const offer = typeof b.offer === "object" ? (b.offer as Offer) : null;
const customer = typeof b.user === "object" ? (b.user as Customer) : null;
return {
id: `booking-${b.id}`,
kind: "booking",
title: offer?.title ?? "Buchung",
subtitle: customer?.name,
date: b.date,
startTime: b.startTime,
endTime: b.endTime,
status: b.status,
appointmentType: b.appointmentType,
href: `/admin/collections/booking-requests/${b.id}`,
};
});
const eventItems: AdminCalendarItem[] = (events as Event[]).map((e) => ({
id: `event-${e.id}`,
kind: "event",
title: e.title,
date: e.date,
startTime: e.startTime ?? e.date,
endTime: e.endTime ?? e.date,
status: "public",
appointmentType: e.isOnline ? "online" : "onsite",
href: `/admin/collections/events/${e.id}`,
}));
return [...bookingItems, ...eventItems].sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime());
}