Compare commits

...

4 commits

Author SHA1 Message Date
Benno Tielen
46b0df992c fix: better description
Some checks are pending
Deploy / deploy (push) Waiting to run
2026-04-29 14:23:23 +02:00
Benno Tielen
ac1eafca64 Merge branch 'map' into staging 2026-04-29 14:22:07 +02:00
Benno Tielen
4766098296 feature: map 2026-04-29 14:21:44 +02:00
Benno Tielen
c0fe32f9f5 feature: chemnitz map 2026-04-23 17:43:38 +02:00
14 changed files with 27267 additions and 1 deletions

View file

@ -17,6 +17,7 @@ import { HorizontalRuleBlock } from '@/collections/blocks/HorizontalRule'
import { BlogSliderBlock } from '@/collections/blocks/BlogSlider'
import { MassTimesBlock } from '@/collections/blocks/MassTimes'
import { CollapsibleImageWithTextBlock } from '@/collections/blocks/CollapsibleImageWithText'
import { CollapsibleMapWithTextBlock } from '@/collections/blocks/CollapsibleMapWithText'
import { CollapsiblesBlock } from '@/collections/blocks/Collapsibles'
import { EventsBlock } from '@/collections/blocks/Events'
import { PublicationAndNewsletterBlock } from '@/collections/blocks/PublicationAndNewsletter'
@ -101,6 +102,7 @@ export const Pages: CollectionConfig = {
HorizontalRuleBlock,
BlogSliderBlock,
CollapsibleImageWithTextBlock,
CollapsibleMapWithTextBlock,
CollapsiblesBlock,
MassTimesBlock,
EventsBlock,

View file

@ -0,0 +1,53 @@
import { Block } from 'payload'
export const CollapsibleMapWithTextBlock: Block = {
slug: 'collapsibleMapWithText',
labels: {
singular: {
de: 'Karte mit Text',
},
plural: {
de: 'Karte mit Text',
},
},
dbName: 'collaps_map',
fields: [
{
name: 'title',
type: 'text',
required: true,
label: {
de: 'Titel',
},
},
{
name: 'text',
type: 'textarea',
required: true,
label: {
de: 'Text',
},
},
{
name: 'content',
type: 'richText',
required: true,
label: {
de: 'Aufklappbarer Inhalt',
},
},
{
name: 'backgroundColor',
type: 'select',
label: {
de: 'Hintergrundfarbe',
},
options: [
{ label: 'Standard', value: 'default' },
{ label: 'Soft', value: 'soft' },
{ label: 'Off-White', value: 'off-white' },
],
defaultValue: 'default',
},
],
}

View file

@ -0,0 +1,18 @@
import { Meta, StoryObj } from '@storybook/nextjs-vite'
import { ChemnitzMap } from './ChemnitzMap'
const meta: Meta<typeof ChemnitzMap> = {
component: ChemnitzMap,
decorators: [
(Story) => (
<div style={{ width: '100%', maxWidth: 1100, margin: '0 auto' }}>
<Story />
</div>
),
],
}
type Story = StoryObj<typeof ChemnitzMap>
export default meta
export const Default: Story = {}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 145 KiB

View file

@ -0,0 +1,30 @@
.map {
width: 100%;
:global(svg) {
width: 100%;
height: 100%;
display: block;
}
}
.parish {
cursor: pointer;
transform-box: fill-box;
transform-origin: center;
transition: transform 0.3s ease;
:global(path.cls-1) {
transition: fill 0.3s ease;
}
&:hover,
&:focus-visible {
transform: scale(1.15);
outline: none;
:global(path.cls-1) {
fill: #0f6898;
}
}
}

View file

@ -14,6 +14,7 @@ import { Banner } from '@/components/Banner/Banner'
import { MainText } from '@/components/MainText/MainText'
import { HR } from '@/components/HorizontalRule/HorizontalRule'
import { CollapsibleImageWithText } from '@/compositions/CollapsibleImageWithText/CollapsibleImageWithText'
import { CollapsibleMapWithText } from '@/compositions/CollapsibleMapWithText/CollapsibleMapWithText'
import { Collapsible } from '@/components/Collapsible/Collapsible'
import { PublicationAndNewsletter } from '@/compositions/PublicationAndNewsletter/PublicationAndNewsletter'
import { ContactPersonCard } from '@/components/ContactPersonCard/ContactPersonCard'
@ -228,6 +229,25 @@ export function Blocks({ content }: BlocksProps) {
)
}
if (item.blockType === 'collapsibleMapWithText') {
const bg = item.backgroundColor === 'soft' || item.backgroundColor === 'off-white'
? item.backgroundColor as 'soft' | 'off-white'
: undefined
return (
<CollapsibleMapWithText
key={item.id}
title={item.title}
text={item.text}
backgroundColor={bg}
content={
item.content
? <HTMLText width={'3/4'} data={item.content} />
: <></>
}
/>
)
}
if (item.blockType === 'collapsibles') {
return (
<Section key={item.id} padding={'small'}>

View file

@ -0,0 +1,44 @@
import { Meta, StoryObj } from '@storybook/nextjs-vite'
import { CollapsibleMapWithText } from './CollapsibleMapWithText'
import { Title } from '@/components/Title/Title'
import { P } from '@/components/Text/Paragraph'
const meta: Meta<typeof CollapsibleMapWithText> = {
component: CollapsibleMapWithText,
}
type Story = StoryObj<typeof CollapsibleMapWithText>
export default meta
export const OurParishes: Story = {
args: {
backgroundColor: 'soft',
title: 'Unsere Gemeinden',
text:
'Unsere Pfarrei umfasst sieben Kirchen in und um Chemnitz. Klicken Sie auf eine Kirche, um mehr über die jeweilige Gemeinde zu erfahren von Gottesdienstzeiten über Gruppen und Kreise bis hin zu Kontaktmöglichkeiten.\n' +
'\n' +
'Jede Gemeinde hat ihren eigenen Charakter und ihre eigene Geschichte, gemeinsam bilden sie ein lebendiges katholisches Netzwerk in der Region.',
content: (
<>
<Title title={'Über unsere Pfarrei'} size={'md'} />
<P width={'3/4'}>
Die Pfarrei wurde gegründet, um die pastorale Arbeit der umliegenden Kirchgemeinden
zu bündeln und gleichzeitig den lokalen Charakter jeder einzelnen Gemeinde zu
bewahren. Auf der Karte sehen Sie die geografische Verteilung von St. Marien im
Stadtzentrum bis St. Antonius in Frankenberg.
</P>
</>
),
},
}
export const NoExtraContent: Story = {
args: {
backgroundColor: 'soft',
title: 'Unsere Gemeinden',
text:
'Unsere Pfarrei umfasst sieben Kirchen in und um Chemnitz. Klicken Sie auf eine Kirche, um mehr über die jeweilige Gemeinde zu erfahren von Gottesdienstzeiten über Gruppen und Kreise bis hin zu Kontaktmöglichkeiten.\n' +
'\n' +
'Jede Gemeinde hat ihren eigenen Charakter und ihre eigene Geschichte, gemeinsam bilden sie ein lebendiges katholisches Netzwerk in der Region.',
},
}

View file

@ -0,0 +1,118 @@
'use client'
import { BackgroundColor, Section } from '@/components/Section/Section'
import { ChemnitzMap } from '@/components/ChemnitzMap/ChemnitzMap'
import { Title } from '@/components/Title/Title'
import styles from './styles.module.scss'
import { Container } from '@/components/Container/Container'
import { P } from '@/components/Text/Paragraph'
import { CollapsibleArrow } from '@/components/CollapsibleArrow/CollapsibleArrow'
import React, { useEffect, useRef, useState } from 'react'
import classNames from 'classnames'
type CollapsibleMapWithTextProps = {
backgroundColor?: BackgroundColor
title: string
text: string
content?: React.ReactNode
}
type MoreInformationProps = {
isCollapsed: boolean
onClick: () => void
}
const MoreInformation = ({ isCollapsed, onClick }: MoreInformationProps) => {
const [direction, setDirection] = useState<'UP' | 'DOWN'>(isCollapsed ? 'DOWN' : 'UP')
const toggleDirection = () => {
setDirection(direction == 'UP' ? 'DOWN' : 'UP')
}
const handleClick = () => {
toggleDirection()
onClick()
}
return (
<button
onClick={handleClick}
className={styles.more}
onMouseEnter={toggleDirection}
onMouseLeave={toggleDirection}
>
Mehr erfahren <CollapsibleArrow direction={direction} />
</button>
)
}
export function CollapsibleMapWithText({
title,
text,
content,
backgroundColor,
}: CollapsibleMapWithTextProps) {
const ref = useRef<HTMLDivElement>(null)
const ref2 = useRef<HTMLDivElement>(null)
const [contentHeight, setContentHeight] = useState(0)
const [isCollapsed, setIsCollapsed] = useState(true)
const collapse = () => {
setIsCollapsed(true)
ref.current?.scrollIntoView({ behavior: 'smooth' })
}
const toggle = () => {
if (isCollapsed) {
setIsCollapsed(false)
setTimeout(() => {
ref2.current?.scrollIntoView({ behavior: 'smooth'})
}, 500)
} else {
collapse()
}
}
useEffect(() => {
if (ref2.current) {
setContentHeight(ref2.current.scrollHeight)
}
}, [ref2])
return (
<div ref={ref}>
<Section backgroundColor={backgroundColor}>
<Container>
<Title title={title} size={'lg'} color={'base'} />
</Container>
<div className={styles.mapContainer}>
<ChemnitzMap/>
</div>
<Container>
<P width={'3/4'}>{text}</P>
{content && <MoreInformation isCollapsed={isCollapsed} onClick={toggle} />}
</Container>
</Section>
{content && (
<div
ref={ref2}
className={classNames({ [styles.content]: true })}
style={{ maxHeight: isCollapsed ? undefined : contentHeight }}
>
<Section backgroundColor={backgroundColor} padding={'small'} paddingBottom={'large'}>
<Container>{content}</Container>
<div className={styles.endButton}>
<CollapsibleArrow direction={'UP'} onClick={collapse} />
</div>
</Section>
</div>
)}
</div>
)
}

View file

@ -0,0 +1,49 @@
@import "template.scss";
$maxWidth: 1100px;
.mapContainer {
transition: background-color 0.2s ease-in-out;
max-width: $maxWidth;
margin: 0 auto 40px auto;
}
.mapContainer:hover {
background-color: #fbfdff;
}
.more {
font-size: 18px;
color: $base-color;
cursor: pointer;
font-weight: bold;
display: inline-flex;
gap: 10px;
border: 0;
background-color: inherit;
align-items: center;
padding: 0;
margin-top: 20px;
}
.content {
transition: max-height 500ms ease-in;
max-height: 0;
overflow: hidden;
}
.endButton {
position: relative;
top: 75px;
text-align: center;
}
.endButton svg {
cursor: pointer;
}
@media screen and (max-width: 576px) {
.endButton {
top: 20px;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TYPE "public"."enum_collaps_map_background_color" AS ENUM('default', 'soft', 'off-white');
CREATE TYPE "public"."enum__collaps_map_v_background_color" AS ENUM('default', 'soft', 'off-white');
CREATE TABLE "collaps_map" (
"_order" integer NOT NULL,
"_parent_id" uuid NOT NULL,
"_path" text NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"title" varchar,
"text" varchar,
"content" jsonb,
"background_color" "enum_collaps_map_background_color" DEFAULT 'default',
"block_name" varchar
);
CREATE TABLE "_collaps_map_v" (
"_order" integer NOT NULL,
"_parent_id" uuid NOT NULL,
"_path" text NOT NULL,
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"title" varchar,
"text" varchar,
"content" jsonb,
"background_color" "enum__collaps_map_v_background_color" DEFAULT 'default',
"_uuid" varchar,
"block_name" varchar
);
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-05-03T12:11:04.492Z';
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-05-03T12:11:04.791Z';
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-05-29T12:11:04.854Z';
ALTER TABLE "collaps_map" ADD CONSTRAINT "collaps_map_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_collaps_map_v" ADD CONSTRAINT "_collaps_map_v_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v"("id") ON DELETE cascade ON UPDATE no action;
CREATE INDEX "collaps_map_order_idx" ON "collaps_map" USING btree ("_order");
CREATE INDEX "collaps_map_parent_id_idx" ON "collaps_map" USING btree ("_parent_id");
CREATE INDEX "collaps_map_path_idx" ON "collaps_map" USING btree ("_path");
CREATE INDEX "_collaps_map_v_order_idx" ON "_collaps_map_v" USING btree ("_order");
CREATE INDEX "_collaps_map_v_parent_id_idx" ON "_collaps_map_v" USING btree ("_parent_id");
CREATE INDEX "_collaps_map_v_path_idx" ON "_collaps_map_v" USING btree ("_path");`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "collaps_map" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "_collaps_map_v" DISABLE ROW LEVEL SECURITY;
DROP TABLE "collaps_map" CASCADE;
DROP TABLE "_collaps_map_v" CASCADE;
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-04-26T13:12:25.662Z';
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-04-26T13:12:25.946Z';
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-05-23T13:12:26.003Z';
DROP TYPE "public"."enum_collaps_map_background_color";
DROP TYPE "public"."enum__collaps_map_v_background_color";`)
}

View file

@ -42,6 +42,7 @@ import * as migration_20260417_111855_event_occurrences from './20260417_111855_
import * as migration_20260417_114727_simplify_recurring_events from './20260417_114727_simplify_recurring_events';
import * as migration_20260423_115311 from './20260423_115311';
import * as migration_20260423_131226_add_event_end_date_time from './20260423_131226_add_event_end_date_time';
import * as migration_20260429_121105_collapsible_map_with_text from './20260429_121105_collapsible_map_with_text';
export const migrations = [
{
@ -262,6 +263,11 @@ export const migrations = [
{
up: migration_20260423_131226_add_event_end_date_time.up,
down: migration_20260423_131226_add_event_end_date_time.down,
name: '20260423_131226_add_event_end_date_time'
name: '20260423_131226_add_event_end_date_time',
},
{
up: migration_20260429_121105_collapsible_map_with_text.up,
down: migration_20260429_121105_collapsible_map_with_text.down,
name: '20260429_121105_collapsible_map_with_text'
},
];

View file

@ -597,6 +597,29 @@ export interface Page {
blockName?: string | null;
blockType: 'collapsibleImageWithText';
}
| {
title: string;
text: string;
content: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
};
backgroundColor?: ('default' | 'soft' | 'off-white') | null;
id?: string | null;
blockName?: string | null;
blockType: 'collapsibleMapWithText';
}
| {
items: {
title: string;
@ -2169,6 +2192,16 @@ export interface PagesSelect<T extends boolean = true> {
id?: T;
blockName?: T;
};
collapsibleMapWithText?:
| T
| {
title?: T;
text?: T;
content?: T;
backgroundColor?: T;
id?: T;
blockName?: T;
};
collapsibles?:
| T
| {