feature: display end times

This commit is contained in:
Benno Tielen 2026-06-05 13:43:17 +02:00
parent 5ee3d48434
commit 1ac42e51db
11 changed files with 26067 additions and 2 deletions

View file

@ -114,6 +114,7 @@ export default async function EventsOverviewPage({
<EventRow
key={e.id}
date={e.date}
end={e.end}
title={e.title}
href={e.href}
location={e.location}

View file

@ -79,6 +79,7 @@ export default async function EventsPage({searchParams}: {
<EventRow
key={e.id}
date={e.date}
end={e.end}
title={e.title}
href={e.href}
location={e.location}

View file

@ -38,6 +38,21 @@ export const EventOccurrences: CollectionConfig = {
},
},
},
{
name: 'endDateTime',
type: 'date',
index: true,
label: {
de: 'Enduhrzeit',
},
admin: {
date: {
pickerAppearance: 'dayAndTime',
timeIntervals: 15,
timeFormat: 'HH:mm',
},
},
},
{
name: 'cancelled',
type: 'checkbox',

View file

@ -33,6 +33,7 @@ export const Events = ({ events, n, schema = 'base' }: EventsProps) => {
<EventRow
key={event.href}
date={event.date}
end={event.end}
title={event.title}
href={event.href}
location={event.location}

View file

@ -42,6 +42,7 @@ type GenerateEventOccurrencesOutput = {
type EventLike = {
id: string
date?: string | null
endDateTime?: string | null
recurrenceType?: EventRecurrenceType | null
recurrenceRules?: RecurrenceRule[] | null
endDate?: string | null
@ -113,6 +114,17 @@ export const regenerateOccurrencesForEvent = async ({
return { created, deleted, skipped: skipped + 1 }
}
// Derive each occurrence's end from the event's duration (end - start),
// re-anchored to the occurrence's own start. A missing or non-positive
// duration yields no end on the occurrences.
const eventEnd = event.endDateTime ? new Date(event.endDateTime) : null
const durationMs =
eventEnd && !Number.isNaN(eventEnd.getTime()) && eventEnd.getTime() > eventDate.getTime()
? eventEnd.getTime() - eventDate.getTime()
: null
const endFor = (start: Date): string | null =>
durationMs === null ? null : new Date(start.getTime() + durationMs).toISOString()
const recurrenceType = event.recurrenceType ?? 'none'
if (recurrenceType === 'none') {
@ -124,6 +136,7 @@ export const regenerateOccurrencesForEvent = async ({
data: {
event: event.id,
date: eventDate.toISOString(),
endDateTime: endFor(eventDate),
cancelled: cancelledDates.has(eventDate.toISOString()),
generated: true,
},
@ -144,6 +157,7 @@ export const regenerateOccurrencesForEvent = async ({
data: {
event: event.id,
date: iso,
endDateTime: endFor(date),
cancelled: cancelledDates.has(iso),
generated: true,
},

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-06-07T11:31:06.259Z';
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-06-07T11:31:06.641Z';
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-07-05T11:31:06.720Z';
ALTER TABLE "event_occurrence" ADD COLUMN "end_date_time" timestamp(3) with time zone;
CREATE INDEX "event_occurrence_end_date_time_idx" ON "event_occurrence" USING btree ("end_date_time");`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
DROP INDEX "event_occurrence_end_date_time_idx";
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-06-07T09:51:11.853Z';
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-06-07T09:51:12.181Z';
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-07-05T09:51:12.265Z';
ALTER TABLE "event_occurrence" DROP COLUMN "end_date_time";`)
}

View file

@ -45,6 +45,7 @@ import * as migration_20260423_131226_add_event_end_date_time from './20260423_1
import * as migration_20260429_121105_collapsible_map_with_text from './20260429_121105_collapsible_map_with_text';
import * as migration_20260504_070356_next_prev_buttons from './20260504_070356_next_prev_buttons';
import * as migration_20260605_095112 from './20260605_095112';
import * as migration_20260605_113107_occurrence_end_time from './20260605_113107_occurrence_end_time';
export const migrations = [
{
@ -280,6 +281,11 @@ export const migrations = [
{
up: migration_20260605_095112.up,
down: migration_20260605_095112.down,
name: '20260605_095112'
name: '20260605_095112',
},
{
up: migration_20260605_113107_occurrence_end_time.up,
down: migration_20260605_113107_occurrence_end_time.down,
name: '20260605_113107_occurrence_end_time'
},
];

View file

@ -21,6 +21,7 @@ import classNames from 'classnames'
type UpcomingOccurrence = {
id: string
date: string
end?: string
title: string
href: string
location: string
@ -204,6 +205,7 @@ export function EventPage(
<EventRow
key={o.id}
date={o.date}
end={o.end}
title={o.title}
href={o.href}
location={o.location}

View file

@ -1180,6 +1180,7 @@ export interface EventOccurrence {
id: string;
event: string | Event;
date: string;
endDateTime?: string | null;
cancelled: boolean;
generated?: boolean | null;
updatedAt: string;
@ -1932,6 +1933,7 @@ export interface EventSelect<T extends boolean = true> {
export interface EventOccurrenceSelect<T extends boolean = true> {
event?: T;
date?: T;
endDateTime?: T;
cancelled?: T;
generated?: T;
updatedAt?: T;

View file

@ -41,6 +41,7 @@ export const eventToPageProps = (
type EventRowItem = {
id: string
date: string
end?: string
title: string
href: string
location: string
@ -51,13 +52,14 @@ export const transformOccurrences = (
occurrences: EventOccurrence[],
): EventRowItem[] => {
return occurrences
.map((o) => {
.map((o): EventRowItem | undefined => {
const event = typeof o.event === 'object' ? o.event : undefined
if (!event) return undefined
return {
id: o.id,
title: event.title,
date: o.date,
end: o.endDateTime ?? undefined,
href: `/veranstaltungen/${event.id}/${o.id}`,
location: typeof event.location === 'object' ? event.location.name : 'Unbekannt',
cancelled: Boolean(event.cancelled || o.cancelled),
@ -75,6 +77,7 @@ export const transformEvents = (events: Event[]): EventRowItem[] => {
id: e.id,
title: e.title,
date: e.date,
end: e.endDateTime ?? undefined,
href: `/veranstaltungen/${e.id}`,
location: typeof e.location === 'object' ? e.location.name : 'Unbekannt',
cancelled: e.cancelled,