diff --git a/web/src/routes/(public)/+page.server.ts b/web/src/routes/(public)/+page.server.ts index a381d1d..c520490 100644 --- a/web/src/routes/(public)/+page.server.ts +++ b/web/src/routes/(public)/+page.server.ts @@ -18,7 +18,7 @@ export const load: PageServerLoad = async ({ fetch }) => { const [weekendRes, seasonRes, statsRes] = await Promise.allSettled([ apiFetch(`/markets?from=${fromDate}&to=${weekendTo}&per_page=4`, { fetch }), - apiFetch(`/markets?from=${fromDate}&per_page=6`, { fetch }), + apiFetch('/season/markets', { fetch }), apiFetch('/markets/stats', { fetch }), ]); diff --git a/web/src/routes/(public)/admin/+layout.svelte b/web/src/routes/(public)/admin/+layout.svelte index 367e58b..1c49a93 100644 --- a/web/src/routes/(public)/admin/+layout.svelte +++ b/web/src/routes/(public)/admin/+layout.svelte @@ -10,6 +10,7 @@ const navItems = [ { href: '/admin/maerkte', label: 'Märkte' }, + { href: '/admin/saisonal', label: 'Saisonal' }, { href: '/admin/discovery', label: 'Discovery' }, { href: '/admin/einstellungen', label: 'Einstellungen' }, ]; diff --git a/web/src/routes/(public)/admin/saisonal/+page.server.ts b/web/src/routes/(public)/admin/saisonal/+page.server.ts new file mode 100644 index 0000000..d70a662 --- /dev/null +++ b/web/src/routes/(public)/admin/saisonal/+page.server.ts @@ -0,0 +1,59 @@ +import { fail } from '@sveltejs/kit'; +import { serverFetch } from '$lib/api/client.server.js'; +import type { MarketSummary } from '$lib/api/types.js'; +import type { Actions, PageServerLoad } from './$types.js'; + +interface SeasonConfig { + slugs: string[]; + count: number; +} + +export const load: PageServerLoad = async ({ cookies, fetch }) => { + const [cfgRes, suggestionsRes] = await Promise.all([ + serverFetch('/admin/settings/season', cookies, { fetch }).catch(() => null), + serverFetch('/admin/season/suggestions', cookies, { fetch }).catch( + () => null, + ), + ]); + + const config: SeasonConfig = cfgRes?.data ?? { slugs: [], count: 6 }; + const allSuggestions: MarketSummary[] = suggestionsRes?.data ?? []; + + const pinnedSet = new Set(config.slugs); + const pinnedMap = new Map(allSuggestions.map((m) => [m.slug, m])); + const pinned: MarketSummary[] = config.slugs + .map((slug) => pinnedMap.get(slug)) + .filter((m): m is MarketSummary => Boolean(m)); + const suggestions = allSuggestions.filter((m) => !pinnedSet.has(m.slug)); + + return { config, pinned, suggestions }; +}; + +export const actions: Actions = { + save: async ({ cookies, fetch, request }) => { + const data = await request.formData(); + const slugsRaw = data.get('slugs'); + const countRaw = data.get('count'); + + if (typeof slugsRaw !== 'string' || typeof countRaw !== 'string') { + return fail(400, { error: 'Ungültige Eingabe.' }); + } + + const slugs = slugsRaw.split(',').map((s) => s.trim()).filter(Boolean); + const count = parseInt(countRaw, 10); + if (!Number.isFinite(count) || count < 1 || count > 24) { + return fail(400, { error: 'Anzahl muss zwischen 1 und 24 liegen.' }); + } + + try { + await serverFetch('/admin/settings/season', cookies, { + method: 'PUT', + body: JSON.stringify({ slugs, count }), + fetch, + }); + return { success: true }; + } catch (err) { + return fail(500, { error: err instanceof Error ? err.message : 'Fehler beim Speichern.' }); + } + }, +}; diff --git a/web/src/routes/(public)/admin/saisonal/+page.svelte b/web/src/routes/(public)/admin/saisonal/+page.svelte new file mode 100644 index 0000000..eebc377 --- /dev/null +++ b/web/src/routes/(public)/admin/saisonal/+page.svelte @@ -0,0 +1,234 @@ + + +
+
+

Saisonale Märkte

+

+ Wähle die Märkte, die auf der Startseite hervorgehoben werden. Fehlende Plätze werden mit + kommenden Märkten in chronologischer Reihenfolge aufgefüllt. +

+
+ + {#if form?.success} +
+ Gespeichert. +
+ {:else if form?.error} +
+ {form.error} +
+ {/if} + +
{ + saving = true; + return async ({ update }) => { + saving = false; + await update(); + }; + }} + > + m.slug).join(',')} /> + + +
+ +
+
+

+ Aktuelle Auswahl ({pinned.length}) +

+ +
+ + {#if pinned.length === 0} +

+ Noch keine Märkte ausgewählt. Klicke auf einen Vorschlag rechts. +

+ {:else} +
    + {#each pinned as market, i} +
  • + + {i + 1}. + +
    +
    + {market.name} +
    +
    + {market.city} · {market.state} · { + fmtDateRange( + market.start_date, + market.end_date, + ) + } +
    +
    +
    + + + +
    +
  • + {/each} +
+ {/if} +
+ + +
+
+

+ Vorschläge — kommende Märkte ({suggestions.length}) +

+
+ {#if suggestions.length === 0} +

+ Keine weiteren Vorschläge verfügbar. +

+ {:else} +
    + {#each suggestions as market} +
  • +
    +
    + {market.name} +
    +
    + {market.city} · {market.state} · { + fmtDateRange( + market.start_date, + market.end_date, + ) + } +
    +
    + +
  • + {/each} +
+ {/if} +
+
+ +
+ +
+
+