mobile desigh test4
#deploy
This commit is contained in:
parent
543f6de552
commit
6a0381ff99
@ -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 { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
import { type Event } from '@/features/event/api/eventService';
|
import { type Event } from '@/features/event/api/eventService';
|
||||||
import { type DashboardModule, type DashboardModuleContext } from '@/components/dashboard/dashboardModuleTypes';
|
import { type DashboardModule, type DashboardModuleContext } from '@/components/dashboard/dashboardModuleTypes';
|
||||||
import { eventDashboardModule } from '@/features/event/dashboardModule';
|
import { eventDashboardModule } from '@/features/event/dashboardModule';
|
||||||
import { quickPostDashboardModule } from '@/features/photopost/dashboardModule';
|
import { quickPostDashboardModule } from '@/features/photopost/dashboardModule';
|
||||||
|
|
||||||
|
const DASHBOARD_ACTIVE_MODULE_KEY = 'kgb.dashboard.activeModuleId';
|
||||||
|
|
||||||
interface DashboardCardProps {
|
interface DashboardCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -49,7 +51,25 @@ interface DashboardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard: React.FC<DashboardProps> = ({ onActionClick, hasActiveEvent, currentEvent }) => {
|
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 = {
|
const ctx: DashboardModuleContext = {
|
||||||
currentEvent,
|
currentEvent,
|
||||||
|
|||||||
@ -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 { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
@ -36,6 +36,7 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
|
|||||||
const [form, setForm] = useState(initialForm);
|
const [form, setForm] = useState(initialForm);
|
||||||
const [mediaPreview, setMediaPreview] = useState<string | null>(null);
|
const [mediaPreview, setMediaPreview] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
const [promotionStartDate, setPromotionStartDate] = useState<string>('');
|
const [promotionStartDate, setPromotionStartDate] = useState<string>('');
|
||||||
const [sectionDates, setSectionDates] = useState<string[]>(() => Array(INITIAL_ARTISTS + 1).fill(''));
|
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 [activeTab, setActiveTab] = useState<'stammdaten' | 'promotion' | 'artists' | 'schedule'>('stammdaten');
|
||||||
const navExpanded = !isMobile;
|
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(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setForm(initialForm);
|
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(() => {
|
useEffect(() => {
|
||||||
if (mode !== 'edit' || !eventId) return;
|
if (mode !== 'edit' || !eventId) return;
|
||||||
|
|
||||||
@ -118,10 +163,13 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
|
|||||||
dates[0] = section1?.promoDate || start;
|
dates[0] = section1?.promoDate || start;
|
||||||
|
|
||||||
artistSlots.forEach((slot, idx) => {
|
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 || '';
|
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 collabUrl = (slot as any).collabUrl || (slot as any).collab_url;
|
||||||
const collabToken = (slot as any).collabToken || (slot as any).collab_token;
|
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) {
|
} else if (collabToken) {
|
||||||
artists[idx].link = `/collab/${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);
|
setSectionDates(dates);
|
||||||
setArtistSections(artists);
|
setArtistSections(artists);
|
||||||
} catch (e) {
|
|
||||||
if (!cancelled) {
|
// 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');
|
setError('Event konnte nicht geladen werden');
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) {
|
if (!cancelled) setLoadingInitial(false);
|
||||||
setLoadingInitial(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -323,6 +346,7 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setSaving(true);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
title: form.name,
|
title: form.name,
|
||||||
@ -356,10 +380,16 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
|
|||||||
} else {
|
} else {
|
||||||
await createEvent(payload);
|
await createEvent(payload);
|
||||||
}
|
}
|
||||||
onSuccess?.();
|
onSuccess();
|
||||||
|
|
||||||
|
// mark clean after successful save
|
||||||
|
initialSnapshotRef.current = currentSnapshot;
|
||||||
|
setIsDirty(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error while saving event', err);
|
console.error('Error saving event:', err);
|
||||||
setError('Event konnte nicht gespeichert werden');
|
setError('Speichern fehlgeschlagen');
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { ArrowLeft, LayoutGrid } from 'lucide-react';
|
|||||||
import { useHeader } from '@/contexts/HeaderContext';
|
import { useHeader } from '@/contexts/HeaderContext';
|
||||||
import EventFormInline from './EventFormInline';
|
import EventFormInline from './EventFormInline';
|
||||||
|
|
||||||
|
const EVENT_WIZARD_STATE_KEY = 'kgb.eventWizard.state';
|
||||||
|
|
||||||
interface EventWizardProps {
|
interface EventWizardProps {
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
onBackToOverview: () => void;
|
onBackToOverview: () => void;
|
||||||
@ -21,6 +23,31 @@ const EventWizard: React.FC<EventWizardProps> = ({ onComplete, onBackToOverview,
|
|||||||
const [activeEventId, setActiveEventId] = useState<string | number | null>(null);
|
const [activeEventId, setActiveEventId] = useState<string | number | null>(null);
|
||||||
const { setHeader, resetHeader } = useHeader();
|
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(() => {
|
const activeEvent = useMemo(() => {
|
||||||
if (!activeEventId) return null;
|
if (!activeEventId) return null;
|
||||||
return events.find((e) => String(e.id) === String(activeEventId)) || null;
|
return events.find((e) => String(e.id) === String(activeEventId)) || null;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user