This commit is contained in:
parent
536163f0e2
commit
79dbf005bf
6 changed files with 453 additions and 115 deletions
|
|
@ -1,144 +1,148 @@
|
|||
import moment from 'moment'
|
||||
import { fetchUpcomingOccurrences } from '@/fetch/eventOccurrences'
|
||||
import { fetchGroup, fetchAllGroups } from '@/fetch/group'
|
||||
import { fetchParish, fetchAllParishes } from '@/fetch/parish'
|
||||
import { transformOccurrences } from '@/utils/dto/events'
|
||||
import { PageHeader } from '@/compositions/PageHeader/PageHeader'
|
||||
import { Section } from '@/components/Section/Section'
|
||||
import { Container } from '@/components/Container/Container'
|
||||
import { Title } from '@/components/Title/Title'
|
||||
import { NextPrevButtons } from '@/components/NextPrevButtons/NextPrevButtons'
|
||||
import moment from 'moment'
|
||||
import { transformOccurrences } from '@/utils/dto/events'
|
||||
import { weekNumber } from '@/utils/week'
|
||||
import { EventRow } from '@/components/EventRow/EventRow'
|
||||
import { fetchHighlightsBetweenDates } from '@/fetch/highlights'
|
||||
import { Row } from '@/components/Flex/Row'
|
||||
import { Col } from '@/components/Flex/Col'
|
||||
import { highlightLink } from '@/utils/dto/highlight'
|
||||
import Error from '@/components/Error/Error'
|
||||
import { NextPrevButtons } from '@/components/NextPrevButtons/NextPrevButtons'
|
||||
import { EventFilterBar } from '@/compositions/EventFilterBar/EventFilterBar'
|
||||
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
|
||||
import Error from '@/components/Error/Error'
|
||||
import { isAuthenticated } from '@/utils/auth'
|
||||
import { Title } from '@/components/Title/Title'
|
||||
import { HR } from '@/components/HorizontalRule/HorizontalRule'
|
||||
import { P } from '@/components/Text/Paragraph'
|
||||
|
||||
const DATE_FMT = 'YYYY-MM-DD'
|
||||
const PAGE_SIZE = 15
|
||||
|
||||
export default async function EventsPage({searchParams}: {
|
||||
searchParams: Promise<{ week: string | undefined }>
|
||||
type Query = {
|
||||
group?: string
|
||||
parish?: string
|
||||
from?: string
|
||||
to?: string
|
||||
page?: string
|
||||
}
|
||||
|
||||
const parseDate = (value: string | undefined): Date | undefined => {
|
||||
if (!value) return undefined
|
||||
const m = moment(value, DATE_FMT, true)
|
||||
return m.isValid() ? m.toDate() : undefined
|
||||
}
|
||||
|
||||
const buildHref = (q: Query, page: number): string => {
|
||||
const params = new URLSearchParams()
|
||||
if (q.group) params.set('group', q.group)
|
||||
if (q.parish) params.set('parish', q.parish)
|
||||
if (q.from) params.set('from', q.from)
|
||||
if (q.to) params.set('to', q.to)
|
||||
if (page > 1) params.set('page', String(page))
|
||||
const qs = params.toString()
|
||||
return qs ? `/veranstaltungen/ueberblick?${qs}` : '/veranstaltungen/ueberblick'
|
||||
}
|
||||
|
||||
export default async function EventsOverviewPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Query>
|
||||
}) {
|
||||
const authenticated = await isAuthenticated()
|
||||
const query = await searchParams
|
||||
|
||||
const authenticated = await isAuthenticated();
|
||||
const query = await searchParams;
|
||||
const limit = 100;
|
||||
let week = query.week;
|
||||
if (!week) {
|
||||
week = weekNumber(moment());
|
||||
const fromDate = parseDate(query.from) ?? moment().startOf('day').toDate()
|
||||
const toDate = parseDate(query.to)
|
||||
|
||||
const parsedPage = parseInt(query.page ?? '1', 10)
|
||||
const page = Number.isFinite(parsedPage) && parsedPage > 0 ? parsedPage : 1
|
||||
|
||||
const [allGroups, allParishes, groupDoc, parishDoc] = await Promise.all([
|
||||
fetchAllGroups(),
|
||||
fetchAllParishes(),
|
||||
query.group ? fetchGroup(query.group) : Promise.resolve(null),
|
||||
query.parish ? fetchParish(query.parish) : Promise.resolve(null),
|
||||
])
|
||||
|
||||
const paginated = await fetchUpcomingOccurrences({
|
||||
limit: PAGE_SIZE,
|
||||
page,
|
||||
fromDate,
|
||||
toDate,
|
||||
groupId: groupDoc?.id,
|
||||
parishId: parishDoc?.id,
|
||||
})
|
||||
|
||||
if (!paginated) {
|
||||
return <Error statusCode={503} message={'Veranstaltungen konnten nicht geladen werden.'} />
|
||||
}
|
||||
|
||||
const fromDate = moment(week, true);
|
||||
const events = transformOccurrences(paginated.docs)
|
||||
|
||||
if (!fromDate.isValid()) {
|
||||
return <Error statusCode={422} message={"Woche fehlerhaft formatiert."}/>
|
||||
const initial = {
|
||||
group: groupDoc?.slug ?? undefined,
|
||||
parish: parishDoc?.slug ?? undefined,
|
||||
from: query.from && parseDate(query.from) ? query.from : undefined,
|
||||
to: query.to && parseDate(query.to) ? query.to : undefined,
|
||||
}
|
||||
|
||||
const toDate = moment(week).add(1, 'week');
|
||||
const lastWeek = moment(week).subtract(1, 'week');
|
||||
|
||||
const paginatedOccurrences = await fetchUpcomingOccurrences(
|
||||
{
|
||||
limit: limit,
|
||||
fromDate: fromDate.toDate(),
|
||||
toDate: toDate.toDate()
|
||||
}
|
||||
);
|
||||
|
||||
const paginatedHighlights = ((await fetchHighlightsBetweenDates(
|
||||
fromDate.toDate(),
|
||||
toDate.toDate(),
|
||||
))?.docs) || [];
|
||||
|
||||
if (!paginatedOccurrences) {
|
||||
return <Error statusCode={503} message={"Veranstaltungen konnten nicht geladen werden."}/>;
|
||||
}
|
||||
|
||||
const events = transformOccurrences(paginatedOccurrences.docs)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title={"Veranstaltungen"}
|
||||
description={"Entdecken Sie unsere kommenden Veranstaltungen und seien Sie dabei! Hier finden Sie alle Termine, Informationen und Highlights auf einen Blick."}
|
||||
/>
|
||||
|
||||
<Section padding={"small"} paddingBottom={"large"}>
|
||||
<Section paddingBottom={"small"}>
|
||||
<Container>
|
||||
<Title
|
||||
title={`Veranstaltungen`}
|
||||
size={"lg"}
|
||||
color={"contrast"}
|
||||
/>
|
||||
<P width={"3/4"}>
|
||||
Entdecken Sie unsere kommenden Veranstaltungen und seien Sie dabei! Hier finden Sie alle Termine, Informationen und Highlights auf einen Blick.
|
||||
</P>
|
||||
<EventFilterBar
|
||||
groups={allGroups.docs.map((g) => ({ slug: g.slug, name: g.name }))}
|
||||
parishes={allParishes.docs.map((p) => ({ slug: p.slug, name: p.name }))}
|
||||
initial={initial}
|
||||
/>
|
||||
</Container>
|
||||
<HR />
|
||||
</Section>
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
<Title
|
||||
color={"contrast"}
|
||||
title={`Woche ${week.substring(5)} - ${week.substring(0, 4)}`}
|
||||
size={"md"}
|
||||
/>
|
||||
<Section padding={'small'} paddingBottom={'large'}>
|
||||
<Container>
|
||||
{events.map((e) => (
|
||||
<EventRow
|
||||
key={e.id}
|
||||
date={e.date}
|
||||
title={e.title}
|
||||
href={e.href}
|
||||
location={e.location}
|
||||
cancelled={e.cancelled}
|
||||
/>
|
||||
))}
|
||||
|
||||
{events.map(e =>
|
||||
<EventRow
|
||||
key={e.id}
|
||||
date={e.date}
|
||||
title={e.title}
|
||||
href={e.href}
|
||||
location={e.location}
|
||||
cancelled={e.cancelled}
|
||||
/>
|
||||
)}
|
||||
|
||||
{events.length == 0 &&
|
||||
<p>
|
||||
Keine Veranstaltungen gefunden
|
||||
</p>
|
||||
}
|
||||
</Col>
|
||||
<Col>
|
||||
{ paginatedHighlights.length > 0 &&
|
||||
<>
|
||||
<Title
|
||||
color={"base"}
|
||||
title={`Highlights`}
|
||||
size={"md"}
|
||||
/>
|
||||
|
||||
{ paginatedHighlights.map(h =>
|
||||
<EventRow
|
||||
key={h.id}
|
||||
date={h.date}
|
||||
title={h.text}
|
||||
href={highlightLink(h)}
|
||||
cancelled={false}
|
||||
showDate={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
{events.length === 0 && <p>Keine Veranstaltungen gefunden</p>}
|
||||
</Container>
|
||||
</Section>
|
||||
<AdminMenu
|
||||
collection={"event"}
|
||||
isAuthenticated={authenticated}
|
||||
/>
|
||||
|
||||
{/*prevents bots indexing till infinity*/}
|
||||
{ events.length > 0 &&
|
||||
<Section padding={"small"}>
|
||||
<AdminMenu collection={'event'} isAuthenticated={authenticated} />
|
||||
|
||||
{(paginated.hasPrevPage || paginated.hasNextPage) && (
|
||||
<Section padding={'small'}>
|
||||
<NextPrevButtons
|
||||
prev={{
|
||||
href: `/veranstaltungen?week=${weekNumber(lastWeek)}`,
|
||||
text: "Vorige Woche"
|
||||
}}
|
||||
next={{
|
||||
href: `/veranstaltungen?week=${weekNumber(toDate)}`,
|
||||
text: "Nächste Woche"
|
||||
}}
|
||||
prev={
|
||||
paginated.hasPrevPage && paginated.prevPage
|
||||
? { href: buildHref(query, paginated.prevPage), text: 'Vorige Seite' }
|
||||
: undefined
|
||||
}
|
||||
next={
|
||||
paginated.hasNextPage && paginated.nextPage
|
||||
? { href: buildHref(query, paginated.nextPage), text: 'Nächste Seite' }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Section>
|
||||
}
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
144
src/app/(home)/veranstaltungen/woche/page.tsx
Normal file
144
src/app/(home)/veranstaltungen/woche/page.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { fetchUpcomingOccurrences } from '@/fetch/eventOccurrences'
|
||||
import { PageHeader } from '@/compositions/PageHeader/PageHeader'
|
||||
import { Section } from '@/components/Section/Section'
|
||||
import { Container } from '@/components/Container/Container'
|
||||
import { Title } from '@/components/Title/Title'
|
||||
import { NextPrevButtons } from '@/components/NextPrevButtons/NextPrevButtons'
|
||||
import moment from 'moment'
|
||||
import { transformOccurrences } from '@/utils/dto/events'
|
||||
import { weekNumber } from '@/utils/week'
|
||||
import { EventRow } from '@/components/EventRow/EventRow'
|
||||
import { fetchHighlightsBetweenDates } from '@/fetch/highlights'
|
||||
import { Row } from '@/components/Flex/Row'
|
||||
import { Col } from '@/components/Flex/Col'
|
||||
import { highlightLink } from '@/utils/dto/highlight'
|
||||
import Error from '@/components/Error/Error'
|
||||
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
|
||||
import { isAuthenticated } from '@/utils/auth'
|
||||
|
||||
|
||||
export default async function EventsPage({searchParams}: {
|
||||
searchParams: Promise<{ week: string | undefined }>
|
||||
}) {
|
||||
|
||||
const authenticated = await isAuthenticated();
|
||||
const query = await searchParams;
|
||||
const limit = 100;
|
||||
let week = query.week;
|
||||
if (!week) {
|
||||
week = weekNumber(moment());
|
||||
}
|
||||
|
||||
const fromDate = moment(week, true);
|
||||
|
||||
if (!fromDate.isValid()) {
|
||||
return <Error statusCode={422} message={"Woche fehlerhaft formatiert."}/>
|
||||
}
|
||||
|
||||
const toDate = moment(week).add(1, 'week');
|
||||
const lastWeek = moment(week).subtract(1, 'week');
|
||||
|
||||
const paginatedOccurrences = await fetchUpcomingOccurrences(
|
||||
{
|
||||
limit: limit,
|
||||
fromDate: fromDate.toDate(),
|
||||
toDate: toDate.toDate()
|
||||
}
|
||||
);
|
||||
|
||||
const paginatedHighlights = ((await fetchHighlightsBetweenDates(
|
||||
fromDate.toDate(),
|
||||
toDate.toDate(),
|
||||
))?.docs) || [];
|
||||
|
||||
if (!paginatedOccurrences) {
|
||||
return <Error statusCode={503} message={"Veranstaltungen konnten nicht geladen werden."}/>;
|
||||
}
|
||||
|
||||
const events = transformOccurrences(paginatedOccurrences.docs)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title={"Veranstaltungen"}
|
||||
description={"Entdecken Sie unsere kommenden Veranstaltungen und seien Sie dabei! Hier finden Sie alle Termine, Informationen und Highlights auf einen Blick."}
|
||||
/>
|
||||
|
||||
<Section padding={"small"} paddingBottom={"large"}>
|
||||
<Container>
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
<Title
|
||||
color={"contrast"}
|
||||
title={`Woche ${week.substring(5)} - ${week.substring(0, 4)}`}
|
||||
size={"md"}
|
||||
/>
|
||||
|
||||
{events.map(e =>
|
||||
<EventRow
|
||||
key={e.id}
|
||||
date={e.date}
|
||||
title={e.title}
|
||||
href={e.href}
|
||||
location={e.location}
|
||||
cancelled={e.cancelled}
|
||||
/>
|
||||
)}
|
||||
|
||||
{events.length == 0 &&
|
||||
<p>
|
||||
Keine Veranstaltungen gefunden
|
||||
</p>
|
||||
}
|
||||
</Col>
|
||||
<Col>
|
||||
{ paginatedHighlights.length > 0 &&
|
||||
<>
|
||||
<Title
|
||||
color={"base"}
|
||||
title={`Highlights`}
|
||||
size={"md"}
|
||||
/>
|
||||
|
||||
{ paginatedHighlights.map(h =>
|
||||
<EventRow
|
||||
key={h.id}
|
||||
date={h.date}
|
||||
title={h.text}
|
||||
href={highlightLink(h)}
|
||||
cancelled={false}
|
||||
showDate={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Section>
|
||||
<AdminMenu
|
||||
collection={"event"}
|
||||
isAuthenticated={authenticated}
|
||||
/>
|
||||
|
||||
{/*prevents bots indexing till infinity*/}
|
||||
{ events.length > 0 &&
|
||||
<Section padding={"small"}>
|
||||
<NextPrevButtons
|
||||
prev={{
|
||||
href: `/veranstaltungen?week=${weekNumber(lastWeek)}`,
|
||||
text: "Vorige Woche"
|
||||
}}
|
||||
next={{
|
||||
href: `/veranstaltungen?week=${weekNumber(toDate)}`,
|
||||
text: "Nächste Woche"
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
107
src/compositions/EventFilterBar/EventFilterBar.tsx
Normal file
107
src/compositions/EventFilterBar/EventFilterBar.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
'use client'
|
||||
|
||||
import { FormEvent, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/Button/Button'
|
||||
import styles from './styles.module.scss'
|
||||
|
||||
type Option = {
|
||||
slug: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type EventFilterBarProps = {
|
||||
groups: Option[]
|
||||
parishes: Option[]
|
||||
initial: {
|
||||
group?: string
|
||||
parish?: string
|
||||
from?: string
|
||||
to?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const EventFilterBar = ({ groups, parishes, initial }: EventFilterBarProps) => {
|
||||
const router = useRouter()
|
||||
const [group, setGroup] = useState(initial.group ?? '')
|
||||
const [parish, setParish] = useState(initial.parish ?? '')
|
||||
const [from, setFrom] = useState(initial.from ?? '')
|
||||
const [to, setTo] = useState(initial.to ?? '')
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
const params = new URLSearchParams()
|
||||
if (group) params.set('group', group)
|
||||
if (parish) params.set('parish', parish)
|
||||
if (from) params.set('from', from)
|
||||
if (to) params.set('to', to)
|
||||
const qs = params.toString()
|
||||
router.push(qs ? `/veranstaltungen/ueberblick?${qs}` : '/veranstaltungen/ueberblick')
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={styles.bar} onSubmit={handleSubmit}>
|
||||
<label className={styles.field}>
|
||||
<span className={styles.label}>Gemeinde</span>
|
||||
<select
|
||||
name="parish"
|
||||
value={parish}
|
||||
onChange={(e) => setParish(e.target.value)}
|
||||
className={styles.input}
|
||||
>
|
||||
<option value="">Alle Gemeinden</option>
|
||||
{parishes.map((p) => (
|
||||
<option key={p.slug} value={p.slug}>
|
||||
{p.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className={styles.field}>
|
||||
<span className={styles.label}>Gruppe</span>
|
||||
<select
|
||||
name="group"
|
||||
value={group}
|
||||
onChange={(e) => setGroup(e.target.value)}
|
||||
className={styles.input}
|
||||
>
|
||||
<option value="">Alle Gruppen</option>
|
||||
{groups.map((g) => (
|
||||
<option key={g.slug} value={g.slug}>
|
||||
{g.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className={styles.field}>
|
||||
<span className={styles.label}>Von</span>
|
||||
<input
|
||||
type="date"
|
||||
name="from"
|
||||
value={from}
|
||||
onChange={(e) => setFrom(e.target.value)}
|
||||
className={styles.input}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className={styles.field}>
|
||||
<span className={styles.label}>Bis</span>
|
||||
<input
|
||||
type="date"
|
||||
name="to"
|
||||
value={to}
|
||||
onChange={(e) => setTo(e.target.value)}
|
||||
className={styles.input}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Button type="submit" size="md">
|
||||
Filtern
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
53
src/compositions/EventFilterBar/styles.module.scss
Normal file
53
src/compositions/EventFilterBar/styles.module.scss
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
@import "template.scss";
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: flex-end;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
flex: 1 1 180px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 11px;
|
||||
color: $shade1;
|
||||
}
|
||||
|
||||
.input {
|
||||
background-color: $shade3;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
border-radius: $border-radius;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
color: #2c2c2c;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
.field {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.input {
|
||||
padding: 10px 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { unstable_cache } from 'next/cache'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
|
||||
|
|
@ -14,3 +15,17 @@ export async function fetchGroup(slug: string, draft: boolean = false) {
|
|||
})
|
||||
return result.docs[0] ?? null
|
||||
}
|
||||
|
||||
export const fetchAllGroups = unstable_cache(
|
||||
async () => {
|
||||
const payload = await getPayload({ config })
|
||||
return payload.find({
|
||||
collection: 'group',
|
||||
limit: 200,
|
||||
sort: 'name',
|
||||
select: { id: true, name: true, slug: true },
|
||||
})
|
||||
},
|
||||
['groups-all'],
|
||||
{ tags: ['groups'], revalidate: 3600 },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { unstable_cache } from 'next/cache'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
|
||||
|
|
@ -14,3 +15,17 @@ export async function fetchParish(slug: string, draft: boolean = false) {
|
|||
})
|
||||
return result.docs[0] ?? null
|
||||
}
|
||||
|
||||
export const fetchAllParishes = unstable_cache(
|
||||
async () => {
|
||||
const payload = await getPayload({ config })
|
||||
return payload.find({
|
||||
collection: 'parish',
|
||||
limit: 200,
|
||||
sort: 'name',
|
||||
select: { id: true, name: true, slug: true },
|
||||
})
|
||||
},
|
||||
['parishes-all'],
|
||||
{ tags: ['parishes'], revalidate: 3600 },
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue