mobile desigh test4

#deploy
This commit is contained in:
martin 2026-01-20 19:29:38 +01:00
parent 543f6de552
commit 6a0381ff99
3 changed files with 119 additions and 42 deletions

View File

@ -1,10 +1,12 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { type Event } from '@/features/event/api/eventService';
import { type DashboardModule, type DashboardModuleContext } from '@/components/dashboard/dashboardModuleTypes';
import { eventDashboardModule } from '@/features/event/dashboardModule';
import { quickPostDashboardModule } from '@/features/photopost/dashboardModule';
const DASHBOARD_ACTIVE_MODULE_KEY = 'kgb.dashboard.activeModuleId';
interface DashboardCardProps {
title: string;
description: string;
@ -49,7 +51,25 @@ interface DashboardProps {
}
const Dashboard: React.FC<DashboardProps> = ({ onActionClick, hasActiveEvent, currentEvent }) => {
const [activeModuleId, setActiveModuleId] = useState<string | null>(null);
const [activeModuleId, setActiveModuleId] = useState<string | null>(() => {
try {
return sessionStorage.getItem(DASHBOARD_ACTIVE_MODULE_KEY);
} catch {
return null;
}
});
useEffect(() => {
try {
if (activeModuleId) {
sessionStorage.setItem(DASHBOARD_ACTIVE_MODULE_KEY, activeModuleId);
} else {
sessionStorage.removeItem(DASHBOARD_ACTIVE_MODULE_KEY);
}
} catch {
// ignore
}
}, [activeModuleId]);
const ctx: DashboardModuleContext = {
currentEvent,

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
@ -36,6 +36,7 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
const [form, setForm] = useState(initialForm);
const [mediaPreview, setMediaPreview] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const fileInputRef = React.useRef<HTMLInputElement>(null);
const [promotionStartDate, setPromotionStartDate] = useState<string>('');
const [sectionDates, setSectionDates] = useState<string[]>(() => Array(INITIAL_ARTISTS + 1).fill(''));
@ -65,6 +66,34 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
const [activeTab, setActiveTab] = useState<'stammdaten' | 'promotion' | 'artists' | 'schedule'>('stammdaten');
const navExpanded = !isMobile;
const initialSnapshotRef = useRef<string | null>(null);
const [isDirty, setIsDirty] = useState(false);
const currentSnapshot = useMemo(() => {
return JSON.stringify({
form,
promotionStartDate,
promotionOffsetDays,
sectionDates,
artistSections,
});
}, [artistSections, form, promotionOffsetDays, promotionStartDate, sectionDates]);
useEffect(() => {
if (initialSnapshotRef.current === null) return;
setIsDirty(initialSnapshotRef.current !== currentSnapshot);
}, [currentSnapshot]);
useEffect(() => {
if (!isDirty) return;
const handler = (e: BeforeUnloadEvent) => {
e.preventDefault();
e.returnValue = '';
};
window.addEventListener('beforeunload', handler);
return () => window.removeEventListener('beforeunload', handler);
}, [isDirty]);
useEffect(() => {
return () => {
setForm(initialForm);
@ -72,6 +101,22 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
};
}, []);
useEffect(() => {
// establish baseline snapshot for create mode immediately
if (mode !== 'create') return;
if (initialSnapshotRef.current !== null) return;
initialSnapshotRef.current = currentSnapshot;
}, [currentSnapshot, mode]);
useEffect(() => {
// establish baseline snapshot for edit mode after initial load finished
if (mode !== 'edit') return;
if (loadingInitial) return;
if (initialSnapshotRef.current !== null) return;
initialSnapshotRef.current = currentSnapshot;
setIsDirty(false);
}, [currentSnapshot, loadingInitial, mode]);
useEffect(() => {
if (mode !== 'edit' || !eventId) return;
@ -118,10 +163,13 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
dates[0] = section1?.promoDate || start;
artistSlots.forEach((slot, idx) => {
if (idx >= artists.length) return;
artists[idx] = {
name: slot.artistName || '',
email: (slot as any).artistEmail || '',
link: (slot as any).artistLink || '',
status: (slot as any).artistStatus || 'Wartet auf Künstler',
};
dates[idx + 1] = slot.promoDate || '';
artists[idx].name = slot.artistName || '';
artists[idx].email = slot.artistInstagram || '';
const collabUrl = (slot as any).collabUrl || (slot as any).collab_url;
const collabToken = (slot as any).collabToken || (slot as any).collab_token;
@ -130,44 +178,19 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
} else if (collabToken) {
artists[idx].link = `/collab/${collabToken}`;
}
const rawStatus = (slot as any).collab_status || (slot as any).collabStatus;
const status = typeof rawStatus === 'string' ? rawStatus.toUpperCase() : '';
// Fallbacks für alte Daten, falls collab_status noch nicht gesetzt ist
const hasArtistData = !!(slot as any).artist_description || !!(slot as any).artistDescription;
const hasLink = !!collabUrl || !!collabToken;
if (status === 'APPROVED') {
artists[idx].status = 'Fertig';
} else if (status === 'UPDATED') {
artists[idx].status = 'Fertig';
} else if (status === 'SUBMITTED') {
artists[idx].status = 'Fertig';
} else if (status === 'INVITED') {
artists[idx].status = 'Wartet auf Künstler';
} else if (status === 'PENDING') {
artists[idx].status = 'Künstler einladen';
} else if (hasArtistData) {
// Legacy: keine Status-Info, aber Beschreibung vorhanden
artists[idx].status = 'Fertig';
} else if (hasLink) {
artists[idx].status = 'Link generiert';
} else {
artists[idx].status = 'Wartet auf Künstler';
}
});
setSectionDates(dates);
setArtistSections(artists);
} catch (e) {
if (!cancelled) {
setError('Event konnte nicht geladen werden');
}
// reset baseline so it can be re-established after all state updates
initialSnapshotRef.current = null;
setIsDirty(false);
} catch (err) {
console.error('Error loading event details', err);
setError('Event konnte nicht geladen werden');
} finally {
if (!cancelled) {
setLoadingInitial(false);
}
if (!cancelled) setLoadingInitial(false);
}
};
@ -323,6 +346,7 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
}
setError(null);
setSaving(true);
const payload = {
title: form.name,
@ -356,10 +380,16 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
} else {
await createEvent(payload);
}
onSuccess?.();
onSuccess();
// mark clean after successful save
initialSnapshotRef.current = currentSnapshot;
setIsDirty(false);
} catch (err) {
console.error('Error while saving event', err);
setError('Event konnte nicht gespeichert werden');
console.error('Error saving event:', err);
setError('Speichern fehlgeschlagen');
} finally {
setSaving(false);
}
};

View File

@ -7,6 +7,8 @@ import { ArrowLeft, LayoutGrid } from 'lucide-react';
import { useHeader } from '@/contexts/HeaderContext';
import EventFormInline from './EventFormInline';
const EVENT_WIZARD_STATE_KEY = 'kgb.eventWizard.state';
interface EventWizardProps {
onComplete: () => void;
onBackToOverview: () => void;
@ -21,6 +23,31 @@ const EventWizard: React.FC<EventWizardProps> = ({ onComplete, onBackToOverview,
const [activeEventId, setActiveEventId] = useState<string | number | null>(null);
const { setHeader, resetHeader } = useHeader();
useEffect(() => {
try {
const raw = sessionStorage.getItem(EVENT_WIZARD_STATE_KEY);
if (!raw) return;
const parsed = JSON.parse(raw) as { showForm?: boolean; activeEventId?: string | number | null };
if (typeof parsed.showForm === 'boolean') setShowForm(parsed.showForm);
if (parsed.activeEventId !== undefined) setActiveEventId(parsed.activeEventId);
} catch {
// ignore
}
// only on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
try {
sessionStorage.setItem(
EVENT_WIZARD_STATE_KEY,
JSON.stringify({ showForm, activeEventId: activeEventId ?? null })
);
} catch {
// ignore
}
}, [activeEventId, showForm]);
const activeEvent = useMemo(() => {
if (!activeEventId) return null;
return events.find((e) => String(e.id) === String(activeEventId)) || null;