Merge pull request 'Anpassungen event erstellung Sprint 1' (#10) from event_create into main
Reviewed-on: #10
This commit is contained in:
commit
46074df578
BIN
assets/eventcreate_foodtable.jpg
Normal file
BIN
assets/eventcreate_foodtable.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
@ -6,6 +6,25 @@
|
||||
--control-min-height: 3rem;
|
||||
--input-min-height: 3.5rem;
|
||||
--card-min-height: 6rem;
|
||||
|
||||
--color-bg: var(--butter);
|
||||
--color-surface: var(--white);
|
||||
--color-surface-soft: var(--butter-light);
|
||||
--color-text: var(--black);
|
||||
--color-text-secondary: rgba(34, 33, 26, 0.8);
|
||||
--color-muted: rgba(34, 33, 26, 0.68);
|
||||
--color-border: rgba(102, 52, 13, 0.16);
|
||||
--color-border-strong: var(--brown);
|
||||
--color-divider: rgba(102, 52, 13, 0.14);
|
||||
--color-primary: var(--olive);
|
||||
--color-primary-hover: var(--olive-dark);
|
||||
--color-progress-bg: rgba(212, 75, 36, 0.18);
|
||||
--color-focus: var(--blue);
|
||||
--color-error: var(--error);
|
||||
--shadow-soft: 0 12px 30px rgba(102, 52, 13, 0.1);
|
||||
--input-border-soft: rgba(102, 52, 13, 0.2);
|
||||
--input-border-focus: rgba(107, 107, 5, 0.45);
|
||||
--input-shadow-focus: 0 0 0 4px rgba(107, 107, 5, 0.12);
|
||||
}
|
||||
|
||||
*,
|
||||
@ -108,6 +127,10 @@ a {
|
||||
padding: var(--space-4) 0 var(--space-7);
|
||||
}
|
||||
|
||||
.submission-success {
|
||||
padding: var(--space-4) 0 var(--space-7);
|
||||
}
|
||||
|
||||
.step--active {
|
||||
display: block;
|
||||
}
|
||||
@ -123,6 +146,7 @@ a {
|
||||
min-height: 60vh;
|
||||
align-content: center;
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-7);
|
||||
}
|
||||
|
||||
.step-copy,
|
||||
@ -185,9 +209,22 @@ h2 {
|
||||
background: linear-gradient(135deg, var(--color-surface), var(--color-surface-soft));
|
||||
}
|
||||
|
||||
.intro-card-emoji {
|
||||
font-size: 2rem;
|
||||
margin-bottom: var(--space-3);
|
||||
.intro-card--image {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.intro-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 10;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
border-radius: 1.875rem;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
label,
|
||||
@ -208,11 +245,13 @@ input[type="number"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: var(--input-min-height);
|
||||
padding: 0.95rem 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
background: var(--color-surface);
|
||||
padding: 1rem 1.1rem;
|
||||
border: 1px solid var(--input-border-soft);
|
||||
border-radius: 1.125rem;
|
||||
background: var(--butter-light);
|
||||
color: var(--color-text);
|
||||
box-shadow: 0 1px 2px rgba(102, 52, 13, 0.04);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@ -220,6 +259,30 @@ textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
input[type="text"]:hover,
|
||||
input[type="date"]:hover,
|
||||
input[type="time"]:hover,
|
||||
input[type="number"]:hover,
|
||||
textarea:hover {
|
||||
border-color: rgba(102, 52, 13, 0.28);
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="date"]:focus,
|
||||
input[type="time"]:focus,
|
||||
input[type="number"]:focus,
|
||||
textarea:focus {
|
||||
border-color: var(--input-border-focus);
|
||||
box-shadow: var(--input-shadow-focus);
|
||||
background: var(--butter-light);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.field-invalid {
|
||||
border-color: var(--tomato) !important;
|
||||
box-shadow: 0 0 0 2px rgba(212, 75, 36, 0.14);
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
@ -238,8 +301,8 @@ textarea {
|
||||
padding: 1rem 1rem 1rem 1.05rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
background: var(--color-surface);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
background: var(--butter-light);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.option-card small {
|
||||
@ -247,6 +310,7 @@ textarea {
|
||||
}
|
||||
|
||||
.option-card:hover {
|
||||
background: var(--olive-light);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
@ -259,7 +323,18 @@ textarea {
|
||||
}
|
||||
|
||||
.option-card:has(input:checked) {
|
||||
border: 2px solid var(--color-border-strong);
|
||||
border: 1px solid var(--color-primary);
|
||||
background: var(--color-primary);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.option-card:has(input:checked) small {
|
||||
color: rgba(247, 246, 230, 0.88);
|
||||
}
|
||||
|
||||
.option-card--invalid {
|
||||
border-color: var(--tomato) !important;
|
||||
box-shadow: 0 0 0 2px rgba(212, 75, 36, 0.14);
|
||||
}
|
||||
|
||||
.counter {
|
||||
@ -281,15 +356,44 @@ textarea {
|
||||
.counter-button {
|
||||
width: var(--control-min-height);
|
||||
height: var(--control-min-height);
|
||||
border: 1px solid var(--color-border);
|
||||
border: 1px solid var(--color-primary);
|
||||
border-radius: 50%;
|
||||
background: var(--color-surface);
|
||||
background: var(--color-primary);
|
||||
color: var(--white);
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
box-shadow: 0 6px 16px rgba(107, 107, 5, 0.18);
|
||||
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.counter-button:hover {
|
||||
background: var(--color-primary-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.counter-button:focus-visible {
|
||||
outline: 3px solid rgba(107, 107, 5, 0.22);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
padding: var(--space-5);
|
||||
border-radius: var(--radius-lg);
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.review-card--success {
|
||||
display: grid;
|
||||
gap: var(--space-5);
|
||||
padding: var(--space-3) 0 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.review-list {
|
||||
@ -300,14 +404,29 @@ textarea {
|
||||
|
||||
.review-item {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
padding-bottom: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-divider);
|
||||
gap: var(--space-2);
|
||||
padding: 1rem 1.1rem;
|
||||
border: 1px solid var(--input-border-soft);
|
||||
border-radius: 1.125rem;
|
||||
background: var(--butter-light);
|
||||
box-shadow: 0 1px 2px rgba(102, 52, 13, 0.04);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.review-item:last-child {
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: 1px solid var(--input-border-soft);
|
||||
}
|
||||
|
||||
.review-item:hover {
|
||||
border-color: rgba(102, 52, 13, 0.28);
|
||||
background: rgba(247, 246, 230, 0.92);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.review-item:focus-visible {
|
||||
outline: 3px solid rgba(107, 107, 5, 0.2);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.review-item dt {
|
||||
@ -320,16 +439,29 @@ textarea {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.submission-success-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flow-footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
margin-top: auto;
|
||||
background: rgba(247, 247, 242, 0.96);
|
||||
backdrop-filter: blur(8px);
|
||||
background: var(--color-bg);
|
||||
backdrop-filter: none;
|
||||
padding-top: var(--space-4);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.progress-wrap {
|
||||
position: relative;
|
||||
width: min(100%, var(--content-width));
|
||||
margin: 0 auto;
|
||||
padding-top: 4.35rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 0.375rem;
|
||||
@ -340,15 +472,50 @@ textarea {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
background: var(--color-primary);
|
||||
background: var(--tomato);
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
.progress-marker {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 0.2rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.progress-marker::after {
|
||||
content: "";
|
||||
width: 0.125rem;
|
||||
height: 1rem;
|
||||
background: var(--tomato);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.progress-marker__circle {
|
||||
width: 2.9rem;
|
||||
height: 2.9rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--tomato);
|
||||
color: var(--butter-light);
|
||||
font-size: 1.35rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
box-shadow: 0 10px 24px rgba(212, 75, 36, 0.18);
|
||||
}
|
||||
|
||||
.flow-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
width: min(100%, var(--content-width));
|
||||
margin: 0 auto;
|
||||
padding: var(--space-4) 0;
|
||||
}
|
||||
|
||||
@ -455,8 +622,10 @@ textarea:focus-visible {
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.step-layout--intro {
|
||||
grid-template-columns: 1.25fr 0.8fr;
|
||||
width: min(100%, 56rem);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: var(--space-8);
|
||||
}
|
||||
|
||||
.field-row {
|
||||
|
||||
@ -43,16 +43,19 @@
|
||||
<h1 id="intro-title">Hey <span id="username">{{username}}</span>, was hast du vor?</h1>
|
||||
<p class="step-text">
|
||||
Erzähl uns von deiner Idee – vom Essen bis zur Stimmung. Ob Dinner, Brunch
|
||||
oder etwas ganz Eigenes – wir helfen dir dabei, dein Event Schritt für Schritt aufzubauen.
|
||||
oder etwas ganz Eigenes – wir helfen dir dabei, dein Event in sieben Schritten aufzubauen.
|
||||
</p>
|
||||
<button type="button" class="button button--primary button--intro" data-start-flow>
|
||||
Los geht’s!
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<aside class="intro-card" aria-label="Hinweis zur Event-Erstellung">
|
||||
<div class="intro-card-emoji" aria-hidden="true">🍽️</div>
|
||||
<p>Aus einer Idee wird Schritt für Schritt dein Event.</p>
|
||||
<aside class="intro-card intro-card--image" aria-label="Stimmungsbild zur Event-Erstellung">
|
||||
<img
|
||||
class="intro-image"
|
||||
src="assets/eventcreate_foodtable.jpg"
|
||||
alt="Ein gedeckter Tisch mit gemeinsamem Essen"
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
@ -69,11 +72,6 @@
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<div class="form-field">
|
||||
<label for="eventTitle">Wie soll dein Event heißen?</label>
|
||||
<input type="text" id="eventTitle" name="eventTitle" required />
|
||||
</div>
|
||||
|
||||
<fieldset class="form-field">
|
||||
<legend>Art des Essens / Eventtyp</legend>
|
||||
|
||||
@ -99,47 +97,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="2" aria-labelledby="step2-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 2</p>
|
||||
<h2 id="step2-title">Was kommt auf den Tisch?</h2>
|
||||
<p class="step-text">
|
||||
Mach uns neugierig. Was kochst du – und wie fühlt sich dein Abend an?
|
||||
Hier entsteht die Geschichte, auf die sich deine Gäste freuen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<div class="form-field">
|
||||
<label for="menuDescription">Was ist das Menü?</label>
|
||||
<textarea id="menuDescription" name="menuDescription" rows="5" required></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="eventDescription">Beschreibung des Event-Abends</label>
|
||||
<textarea id="eventDescription" name="eventDescription" rows="6" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="3" aria-labelledby="step3-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 3</p>
|
||||
<h2 id="step3-title">Wen lädst du ein?</h2>
|
||||
<p class="step-text">
|
||||
Wie viele Gäste passen zu deinem Event? Und gibt es etwas, das du bei
|
||||
Ernährung oder Unverträglichkeiten beachten möchtest?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<fieldset class="form-field">
|
||||
<legend>Maximale Personenanzahl</legend>
|
||||
|
||||
@ -171,7 +129,21 @@
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="2" aria-labelledby="step2-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 2</p>
|
||||
<h2 id="step2-title">Was kommt auf den Tisch?</h2>
|
||||
<p class="step-text">
|
||||
Mach uns neugierig. Was gibt es zu essen? Gibt es eine bestimmte Ernährungsform oder ein Motto? Je mehr du verrätst, desto besser können sich deine Gäste auf dein Event freuen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<fieldset class="form-field">
|
||||
<legend>Ernährungsform</legend>
|
||||
|
||||
@ -194,6 +166,25 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="menuDescription">Was ist das Menü?</label>
|
||||
<textarea id="menuDescription" name="menuDescription" rows="5" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="3" aria-labelledby="step3-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 3</p>
|
||||
<h2 id="step3-title">Gibt es etwas zu beachten?</h2>
|
||||
<p class="step-text">
|
||||
Gibt es Allergien, Unverträglichkeiten oder andere Hinweise, die für dein Event wichtig sind? So wissen deine Gäste gleich, worauf sie sich einstellen können.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<fieldset class="form-field">
|
||||
<legend>Allergene / Unverträglichkeiten</legend>
|
||||
<p class="field-hint">Optional – nur auswählen, wenn es für dein Event relevant ist.</p>
|
||||
@ -214,12 +205,12 @@
|
||||
<span>ohne Nüsse</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="allergiesOther">Weitere Unverträglichkeiten oder Hinweise (optional)</label>
|
||||
<textarea id="allergiesOther" name="allergiesOther" rows="3"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -230,8 +221,7 @@
|
||||
<p class="step-kicker">Schritt 4</p>
|
||||
<h2 id="step4-title">Wann findet dein Event statt?</h2>
|
||||
<p class="step-text">
|
||||
Wähle Datum und Uhrzeit – und sag uns, wo dein Event stattfindet.
|
||||
Keine Sorge: Die genaue Adresse sehen Gäste erst nach der Buchung.
|
||||
Wähle Datum und Uhrzeit für dein Event. So können deine Gäste direkt einschätzen, ob der Termin für sie passt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -247,7 +237,21 @@
|
||||
<input type="time" id="eventTime" name="eventTime" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="5" aria-labelledby="step5-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 5</p>
|
||||
<h2 id="step5-title">Wo findet dein Event statt?</h2>
|
||||
<p class="step-text">
|
||||
Sag uns, wo dein Event stattfindet. Keine Sorge: Die genaue Adresse sehen Gäste erst nach der Buchung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<div class="form-field">
|
||||
<label for="eventAddress">Adresse</label>
|
||||
<input type="text" id="eventAddress" name="eventAddress" autocomplete="street-address" required />
|
||||
@ -261,82 +265,112 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="5" aria-labelledby="step5-title">
|
||||
<section class="step" data-step="6" aria-labelledby="step6-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 5</p>
|
||||
<h2 id="step5-title">Alles bereit für deine Gäste?</h2>
|
||||
<p class="step-kicker">Schritt 6</p>
|
||||
<h2 id="step6-title">Gib deinem Event den letzten Schliff.</h2>
|
||||
<p class="step-text">
|
||||
Schau dir dein Event nochmal in Ruhe an. Passt alles?
|
||||
Dann kannst du es jetzt veröffentlichen und Gäste einladen.
|
||||
Jetzt bekommt dein Event seinen Namen und die Atmosphäre, die Lust aufs Dabeisein macht.
|
||||
Ein klarer Titel (z.B. "Italienische Tavolata") und ein guter Beschreibungstext (Ablauf etc.) machen den Unterschied.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="step-fields">
|
||||
<div class="form-field">
|
||||
<label for="eventTitle">Wie soll dein Event heißen?</label>
|
||||
<input type="text" id="eventTitle" name="eventTitle" required />
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="eventDescription">Beschreibung des Event-Abends</label>
|
||||
<textarea id="eventDescription" name="eventDescription" rows="6" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" data-step="7" aria-labelledby="step7-title">
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Schritt 7</p>
|
||||
<h2 id="step7-title">Dein Event auf einen Blick.</h2>
|
||||
<p class="step-text">
|
||||
Schau dir alle Details nochmal in Ruhe an. Wenn alles passt,
|
||||
kannst du dein Event jetzt veröffentlichen und Gäste einladen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="review-card" aria-live="polite">
|
||||
<dl class="review-list">
|
||||
<div class="review-item">
|
||||
<dt>Eventtitel</dt>
|
||||
<dd data-review="eventTitle">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="1" data-edit-field="eventType" role="button" tabindex="0" aria-label="Eventtyp bearbeiten">
|
||||
<dt>Eventtyp</dt>
|
||||
<dd data-review="eventType">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Menü</dt>
|
||||
<dd data-review="menuDescription">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<dt>Event-Abend</dt>
|
||||
<dd data-review="eventDescription">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="1" data-edit-field="maxGuests" role="button" tabindex="0" aria-label="Maximale Personenanzahl bearbeiten">
|
||||
<dt>Maximale Personenanzahl</dt>
|
||||
<dd data-review="maxGuests">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="2" data-edit-field="dietType" role="button" tabindex="0" aria-label="Ernährungsform bearbeiten">
|
||||
<dt>Ernährungsform</dt>
|
||||
<dd data-review="dietType">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="2" data-edit-field="menuDescription" role="button" tabindex="0" aria-label="Menü bearbeiten">
|
||||
<dt>Menü</dt>
|
||||
<dd data-review="menuDescription">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item" data-edit-step="3" data-edit-field="allergiesOther" role="button" tabindex="0" aria-label="Allergene und Unverträglichkeiten bearbeiten">
|
||||
<dt>Allergene / Unverträglichkeiten</dt>
|
||||
<dd data-review="allergies">Keine Angabe</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="4" data-edit-field="eventDate" role="button" tabindex="0" aria-label="Datum bearbeiten">
|
||||
<dt>Datum</dt>
|
||||
<dd data-review="eventDate">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="4" data-edit-field="eventTime" role="button" tabindex="0" aria-label="Uhrzeit bearbeiten">
|
||||
<dt>Uhrzeit</dt>
|
||||
<dd data-review="eventTime">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="5" data-edit-field="eventAddress" role="button" tabindex="0" aria-label="Adresse bearbeiten">
|
||||
<dt>Adresse</dt>
|
||||
<dd data-review="eventAddress">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item">
|
||||
<div class="review-item" data-edit-step="5" data-edit-field="eventCity" role="button" tabindex="0" aria-label="Ort bearbeiten">
|
||||
<dt>Ort</dt>
|
||||
<dd data-review="eventCity">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item" data-edit-step="6" data-edit-field="eventTitle" role="button" tabindex="0" aria-label="Eventtitel bearbeiten">
|
||||
<dt>Eventtitel</dt>
|
||||
<dd data-review="eventTitle">–</dd>
|
||||
</div>
|
||||
|
||||
<div class="review-item" data-edit-step="6" data-edit-field="eventDescription" role="button" tabindex="0" aria-label="Event-Abend bearbeiten">
|
||||
<dt>Event-Abend</dt>
|
||||
<dd data-review="eventDescription">–</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="flow-footer" id="flowFooter" hidden>
|
||||
<div class="progress" aria-hidden="true">
|
||||
<div class="progress-wrap" aria-hidden="true">
|
||||
<div class="progress-marker" id="progressMarker">
|
||||
<span class="progress-marker__circle" id="progressMarkerLabel">1</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<span id="progressBar" class="progress-bar"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-actions">
|
||||
<button type="button" id="backButton" class="button button--text">Zurück</button>
|
||||
@ -347,6 +381,31 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section
|
||||
id="submissionSuccess"
|
||||
class="submission-success"
|
||||
aria-labelledby="success-title"
|
||||
aria-live="polite"
|
||||
hidden
|
||||
>
|
||||
<div class="step-layout">
|
||||
<div class="step-copy">
|
||||
<p class="step-kicker">Event erstellt</p>
|
||||
<h2 id="success-title">Dein Event ist ready.</h2>
|
||||
<p class="step-text">
|
||||
Sieht gut aus: Deine Idee ist jetzt live und bereit für Gäste.
|
||||
Im Profil kannst du dein Event anschauen, verwalten oder direkt das nächste planen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="review-card review-card--success">
|
||||
<div class="submission-success-actions">
|
||||
<a class="button button--primary" href="event_overview.html">Weiter zu deinem Profil</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
|
||||
@ -8,9 +8,13 @@ const steps = Array.from(document.querySelectorAll(".step"));
|
||||
const backButton = document.getElementById("backButton");
|
||||
const nextButton = document.getElementById("nextButton");
|
||||
const progressBar = document.getElementById("progressBar");
|
||||
const progressMarker = document.getElementById("progressMarker");
|
||||
const progressMarkerLabel = document.getElementById("progressMarkerLabel");
|
||||
const errorMessage = document.getElementById("errorMessage");
|
||||
const usernameElement = document.getElementById("username");
|
||||
const flowFooter = document.getElementById("flowFooter");
|
||||
const submissionSuccess = document.getElementById("submissionSuccess");
|
||||
const EVENTS_STORAGE_KEY = "socialCookingEvents";
|
||||
|
||||
// =============================
|
||||
// STATE: aktueller Schritt im Flow
|
||||
@ -27,7 +31,9 @@ const nextLabels = {
|
||||
2: "Weiter",
|
||||
3: "Weiter",
|
||||
4: "Weiter",
|
||||
5: "Event veröffentlichen"
|
||||
5: "Weiter",
|
||||
6: "Weiter",
|
||||
7: "Event veröffentlichen"
|
||||
};
|
||||
|
||||
// Demo-Wert: Später könnte der Name z. B. aus einem User-Profil kommen
|
||||
@ -57,6 +63,39 @@ function setErrorMessage(message = "") {
|
||||
errorMessage.textContent = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt alle Fehlermarkierungen innerhalb eines Schritts.
|
||||
*/
|
||||
function clearStepInvalidState(stepIndex) {
|
||||
if (!steps[stepIndex]) return;
|
||||
|
||||
steps[stepIndex]
|
||||
.querySelectorAll(".field-invalid, .option-card--invalid")
|
||||
.forEach(element => {
|
||||
element.classList.remove("field-invalid", "option-card--invalid");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Markiert ein einzelnes Feld visuell als ungültig.
|
||||
*/
|
||||
function markFieldInvalid(field) {
|
||||
field.classList.add("field-invalid");
|
||||
}
|
||||
|
||||
/**
|
||||
* Markiert eine ganze Radio-Gruppe visuell als ungültig.
|
||||
*/
|
||||
function markRadioGroupInvalid(group) {
|
||||
group.forEach(field => {
|
||||
const card = field.closest(".option-card");
|
||||
|
||||
if (card) {
|
||||
card.classList.add("option-card--invalid");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 2: Schritt anzeigen & Oberfläche aktualisieren
|
||||
@ -71,6 +110,8 @@ function setErrorMessage(message = "") {
|
||||
*/
|
||||
function showStep(index) {
|
||||
currentStep = index;
|
||||
submissionSuccess.hidden = true;
|
||||
clearStepInvalidState(index);
|
||||
|
||||
// Nur der aktuelle Schritt soll sichtbar sein
|
||||
steps.forEach((step, stepIndex) => {
|
||||
@ -115,12 +156,21 @@ function updateFlowVisibility(stepIndex) {
|
||||
*/
|
||||
function updateProgressBar(stepIndex, totalStepIndex) {
|
||||
let progress = 0;
|
||||
let markerPosition = 0;
|
||||
let markerStep = 1;
|
||||
let markerTransform = "translateX(-50%)";
|
||||
|
||||
if (stepIndex > 0) {
|
||||
progress = ((stepIndex - 1) / (totalStepIndex - 1)) * 100;
|
||||
markerPosition = ((stepIndex - 1) / (totalStepIndex - 1)) * 100;
|
||||
markerStep = stepIndex;
|
||||
}
|
||||
|
||||
progressBar.style.width = `${progress}%`;
|
||||
progressMarker.style.left = `${markerPosition}%`;
|
||||
progressMarker.style.transform = markerTransform;
|
||||
progressMarker.hidden = stepIndex === 0;
|
||||
progressMarkerLabel.textContent = String(markerStep);
|
||||
}
|
||||
|
||||
|
||||
@ -225,7 +275,18 @@ function buildAllergiesReviewValue() {
|
||||
* und schreibt sie gesammelt in die Review-Ansicht.
|
||||
*/
|
||||
function updateReview() {
|
||||
const reviewValues = {
|
||||
const reviewValues = getReviewValues();
|
||||
|
||||
Object.entries(reviewValues).forEach(([key, value]) => {
|
||||
updateReviewField(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest alle wichtigen Formularwerte gesammelt aus.
|
||||
*/
|
||||
function getReviewValues() {
|
||||
return {
|
||||
eventTitle: getFieldValue("eventTitle"),
|
||||
eventType: getFieldValue("eventType"),
|
||||
menuDescription: getFieldValue("menuDescription"),
|
||||
@ -238,12 +299,137 @@ function updateReview() {
|
||||
eventAddress: getFieldValue("eventAddress"),
|
||||
eventCity: getFieldValue("eventCity")
|
||||
};
|
||||
|
||||
Object.entries(reviewValues).forEach(([key, value]) => {
|
||||
updateReviewField(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest lokal gespeicherte Events robust aus dem Browser-Storage.
|
||||
*/
|
||||
function getStoredEvents() {
|
||||
try {
|
||||
const stored = localStorage.getItem(EVENTS_STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch (error) {
|
||||
console.error("Lokale Events konnten nicht gelesen werden:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert die komplette Eventliste zurück in den Browser-Storage.
|
||||
*/
|
||||
function setStoredEvents(events) {
|
||||
localStorage.setItem(EVENTS_STORAGE_KEY, JSON.stringify(events));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatiert ein ISO-Datum in das bestehende Eventformat der Demo-Daten.
|
||||
*/
|
||||
function formatDateForStorage(value) {
|
||||
if (!value) return "";
|
||||
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
|
||||
const monthMap = {
|
||||
0: "JAN",
|
||||
1: "FEB",
|
||||
2: "MRZ",
|
||||
3: "APR",
|
||||
4: "MAI",
|
||||
5: "JUN",
|
||||
6: "JUL",
|
||||
7: "AUG",
|
||||
8: "SEP",
|
||||
9: "OKT",
|
||||
10: "NOV",
|
||||
11: "DEZ"
|
||||
};
|
||||
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const month = monthMap[date.getMonth()];
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${day}. ${month}. ${year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatiert die Zeit in das bestehende Eventformat der Demo-Daten.
|
||||
*/
|
||||
function formatTimeForStorage(value) {
|
||||
return value ? `${value} UHR` : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Zerlegt das Menü-Textarea in saubere Listenpunkte.
|
||||
*/
|
||||
function buildMenuItems(value) {
|
||||
return value
|
||||
.split("\n")
|
||||
.map(item => item.replace(/^[•-]\s*/, "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leitet den gewählten Eventtyp in die Kategorien der Übersicht über.
|
||||
*/
|
||||
function mapEventTypeToCategory(value) {
|
||||
const categoryMap = {
|
||||
Brunch: "BRUNCH",
|
||||
Lunch: "LUNCH",
|
||||
Dinner: "DINNER",
|
||||
"Kaffee + Kuchen": "COFFEE"
|
||||
};
|
||||
|
||||
return categoryMap[value] || value.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut aus den Formulardaten ein lokal speicherbares Event-Objekt.
|
||||
*/
|
||||
function buildStoredEvent() {
|
||||
const eventType = getFieldValue("eventType");
|
||||
const dietType = getFieldValue("dietType");
|
||||
const menuDescription = form.elements.menuDescription.value.trim();
|
||||
const eventDescription = form.elements.eventDescription.value.trim();
|
||||
const eventDate = form.elements.eventDate.value;
|
||||
const eventTime = form.elements.eventTime.value;
|
||||
const eventCity = form.elements.eventCity.value.trim();
|
||||
|
||||
return {
|
||||
id: Date.now(),
|
||||
title: form.elements.eventTitle.value.trim(),
|
||||
location: eventCity,
|
||||
address: form.elements.eventAddress.value.trim(),
|
||||
date: formatDateForStorage(eventDate),
|
||||
time: formatTimeForStorage(eventTime),
|
||||
category: mapEventTypeToCategory(eventType),
|
||||
diet: dietType,
|
||||
spots: Number(form.elements.maxGuests.value),
|
||||
host: {
|
||||
name: usernameElement.textContent.trim() || "Host",
|
||||
initial: (usernameElement.textContent.trim().charAt(0) || "H").toUpperCase()
|
||||
},
|
||||
hostMessage: [eventDescription],
|
||||
menu: buildMenuItems(menuDescription),
|
||||
specifications: getCheckboxValues("allergies") === "Keine Angabe"
|
||||
? []
|
||||
: getCheckboxValues("allergies").split(", ").filter(Boolean),
|
||||
allergiesNote: form.elements.allergiesOther.value.trim(),
|
||||
participants: [usernameElement.textContent.trim() || "Host"],
|
||||
gallery: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
source: "local"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert das aktuell erstellte Event lokal im Browser.
|
||||
*/
|
||||
function saveCurrentEvent() {
|
||||
const storedEvents = getStoredEvents();
|
||||
const nextEvents = [buildStoredEvent(), ...storedEvents];
|
||||
setStoredEvents(nextEvents);
|
||||
}
|
||||
|
||||
// =============================
|
||||
// STEP 5: Validierung
|
||||
@ -263,6 +449,7 @@ function validateCurrentStep() {
|
||||
if (currentStep === 0 || currentStep === lastStep) return true;
|
||||
|
||||
const fields = getStepFields(currentStep);
|
||||
clearStepInvalidState(currentStep);
|
||||
|
||||
// Zuerst Radio-Gruppen prüfen
|
||||
const radioCheck = validateRadioGroups(fields);
|
||||
@ -297,6 +484,7 @@ function validateRadioGroups(fields) {
|
||||
const selected = group.some(f => f.checked);
|
||||
|
||||
if (required && !selected) {
|
||||
markRadioGroupInvalid(group);
|
||||
return {
|
||||
isValid: false,
|
||||
message: "Bitte wähle eine Option aus."
|
||||
@ -318,6 +506,7 @@ function validateRequiredFields(fields) {
|
||||
if (field.type === "radio" || field.type === "checkbox") continue;
|
||||
|
||||
if (!field.checkValidity()) {
|
||||
markFieldInvalid(field);
|
||||
return {
|
||||
isValid: false,
|
||||
message: "Bitte fülle alle Pflichtfelder aus."
|
||||
@ -374,11 +563,12 @@ function handleNextClick() {
|
||||
*/
|
||||
function handleFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
// 1. Feedback geben
|
||||
alert("Dein Event wurde erfolgreich veröffentlicht!");
|
||||
|
||||
// 2. Weiterleiten (z. B. zur Event-Übersicht)
|
||||
window.location.href = "event_overview.html";
|
||||
saveCurrentEvent();
|
||||
steps.forEach(step => step.classList.remove("step--active"));
|
||||
flowFooter.hidden = true;
|
||||
submissionSuccess.hidden = false;
|
||||
setErrorMessage("");
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
|
||||
@ -412,6 +602,126 @@ function updateCounterValue(input, change) {
|
||||
input.value = Math.max(min, currentValue + change);
|
||||
}
|
||||
|
||||
/**
|
||||
* Macht aus "-"+Enter im Menüfeld eine einfache Bullet-Liste.
|
||||
*/
|
||||
function registerMenuBulletHandler() {
|
||||
const menuField = document.getElementById("menuDescription");
|
||||
|
||||
if (!menuField) return;
|
||||
|
||||
menuField.addEventListener("keydown", event => {
|
||||
if (event.key !== "Enter") return;
|
||||
|
||||
const { selectionStart, selectionEnd, value } = menuField;
|
||||
const lineStart = value.lastIndexOf("\n", selectionStart - 1) + 1;
|
||||
const lineEnd = value.indexOf("\n", selectionStart);
|
||||
const currentLineEnd = lineEnd === -1 ? value.length : lineEnd;
|
||||
const currentLine = value.slice(lineStart, currentLineEnd);
|
||||
const trimmedLine = currentLine.trim();
|
||||
|
||||
if (trimmedLine !== "-" && !currentLine.startsWith("• ")) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const isEmptyBullet = currentLine.trim() === "•";
|
||||
|
||||
if (isEmptyBullet) {
|
||||
const beforeLine = value.slice(0, lineStart);
|
||||
const afterLine = value.slice(currentLineEnd);
|
||||
const separator = beforeLine.endsWith("\n") || afterLine.startsWith("\n") ? "" : "\n";
|
||||
const nextValue = `${beforeLine}${separator}${afterLine}`.replace(/\n{3,}/g, "\n\n");
|
||||
|
||||
menuField.value = nextValue;
|
||||
const caretPosition = Math.min(lineStart, nextValue.length);
|
||||
menuField.setSelectionRange(caretPosition, caretPosition);
|
||||
menuField.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
return;
|
||||
}
|
||||
|
||||
const bulletLine = currentLine.startsWith("• ") ? currentLine : currentLine.replace("-", "•");
|
||||
const updatedLine = bulletLine.startsWith("• ") ? bulletLine : `• ${trimmedLine.slice(1).trimStart()}`;
|
||||
const beforeLine = value.slice(0, lineStart);
|
||||
const afterLine = value.slice(currentLineEnd);
|
||||
const nextValue = `${beforeLine}${updatedLine}\n• ${afterLine}`;
|
||||
const caretPosition = beforeLine.length + updatedLine.length + 3;
|
||||
|
||||
menuField.value = nextValue;
|
||||
menuField.setSelectionRange(caretPosition, caretPosition);
|
||||
menuField.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Springt aus der Review zurück zum passenden Schritt
|
||||
* und fokussiert das gewünschte Feld für direktes Weiterbearbeiten.
|
||||
*/
|
||||
function registerReviewEditHandlers() {
|
||||
document.querySelectorAll(".review-item[data-edit-step]").forEach(item => {
|
||||
const activateEdit = () => {
|
||||
const stepIndex = Number(item.dataset.editStep);
|
||||
const fieldName = item.dataset.editField;
|
||||
|
||||
showStep(stepIndex);
|
||||
focusFieldByName(fieldName);
|
||||
};
|
||||
|
||||
item.addEventListener("click", activateEdit);
|
||||
item.addEventListener("keydown", event => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
activateEdit();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt Fehlermarkierungen, sobald der User ein Feld korrigiert.
|
||||
*/
|
||||
function registerValidationFeedbackHandlers() {
|
||||
form.querySelectorAll("input, textarea, select").forEach(field => {
|
||||
const clearInvalidState = () => {
|
||||
field.classList.remove("field-invalid");
|
||||
|
||||
if (field.type === "radio") {
|
||||
const group = Array.from(form.querySelectorAll(`input[name="${field.name}"]`));
|
||||
const hasSelection = group.some(item => item.checked);
|
||||
|
||||
if (hasSelection) {
|
||||
group.forEach(item => {
|
||||
const card = item.closest(".option-card");
|
||||
|
||||
if (card) {
|
||||
card.classList.remove("option-card--invalid");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
field.addEventListener("input", clearInvalidState);
|
||||
field.addEventListener("change", clearInvalidState);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Fokus auf ein bestimmtes Feld oder die erste Option einer Radio-Gruppe.
|
||||
*/
|
||||
function focusFieldByName(fieldName) {
|
||||
const field = form.elements[fieldName];
|
||||
|
||||
if (!field) return;
|
||||
|
||||
const focusTarget = field instanceof RadioNodeList ? field[0] : field;
|
||||
|
||||
if (focusTarget && typeof focusTarget.focus === "function") {
|
||||
window.setTimeout(() => {
|
||||
focusTarget.focus();
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============================
|
||||
// STEP 9: Alles starten
|
||||
@ -438,8 +748,12 @@ function initEventCreationFlow() {
|
||||
|
||||
// Counter aktivieren
|
||||
registerCounterHandlers();
|
||||
registerMenuBulletHandler();
|
||||
registerValidationFeedbackHandlers();
|
||||
registerReviewEditHandlers();
|
||||
|
||||
// Startzustand: Intro anzeigen
|
||||
submissionSuccess.hidden = true;
|
||||
showStep(0);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||
// -------------------------------------------------------------
|
||||
// DOM entry point and shared asset path.
|
||||
// -------------------------------------------------------------
|
||||
@ -14,10 +15,21 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
function getStoredEvents() {
|
||||
try {
|
||||
const stored = localStorage.getItem(EVENTS_STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch (error) {
|
||||
console.error('Lokale Events konnten nicht gelesen werden.', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch data source and resolve the matching event record.
|
||||
try {
|
||||
const response = await fetch('data/events.json');
|
||||
const allEvents = await response.json();
|
||||
const apiEvents = await response.json();
|
||||
const allEvents = [...getStoredEvents(), ...apiEvents];
|
||||
const event = allEvents.find(e => e.id === eventId);
|
||||
|
||||
if (event) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||
// -------------------------------------------------------------
|
||||
// DOM references used throughout the page lifecycle.
|
||||
// -------------------------------------------------------------
|
||||
@ -14,6 +15,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let allEvents = [];
|
||||
let activeCategory = 'ALLE';
|
||||
|
||||
function getStoredEvents() {
|
||||
try {
|
||||
const stored = localStorage.getItem(EVENTS_STORAGE_KEY);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch (error) {
|
||||
console.error('Lokale Events konnten nicht gelesen werden.', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Initial data bootstrap:
|
||||
// 1) fetch JSON,
|
||||
@ -24,7 +35,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
async function fetchEvents() {
|
||||
try {
|
||||
const response = await fetch('data/events.json');
|
||||
allEvents = await response.json();
|
||||
const apiEvents = await response.json();
|
||||
const localEvents = getStoredEvents();
|
||||
allEvents = [...localEvents, ...apiEvents];
|
||||
populateMetaFilters();
|
||||
|
||||
const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE';
|
||||
@ -62,6 +75,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Convert localized event date (e.g. 19. MÄR. 2026) into ISO format for date input comparison.
|
||||
function parseEventDateToIso(dateString) {
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
|
||||
return dateString;
|
||||
}
|
||||
|
||||
const months = {
|
||||
JAN: '01',
|
||||
FEB: '02',
|
||||
@ -92,6 +109,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Convert short month notation into full German month label for UI display.
|
||||
function formatEventDate(dateString) {
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
|
||||
const [year, month, day] = dateString.split('-');
|
||||
return `${Number(day)}. ${['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'][Number(month) - 1]} ${year}`;
|
||||
}
|
||||
|
||||
const labels = {
|
||||
JAN: 'Januar',
|
||||
FEB: 'Februar',
|
||||
@ -122,7 +144,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Normalize time label from UHR to Uhr for consistent typography.
|
||||
function formatEventTime(timeString) {
|
||||
return timeString.replace('UHR', 'Uhr').trim();
|
||||
if (!timeString) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return timeString.includes('UHR')
|
||||
? timeString.replace('UHR', 'Uhr').trim()
|
||||
: `${timeString} Uhr`;
|
||||
}
|
||||
|
||||
// Safely verify whether a value exists in the given select element.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user