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
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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());
|
||||
}
|
||||
Reference in New Issue
Block a user