new desighn finale Test1

#deploy
This commit is contained in:
martin 2026-01-20 18:23:26 +01:00
parent 8377e822b9
commit 26fcadc747
10 changed files with 160 additions and 52 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/LogoFull.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -4,12 +4,22 @@ import { LogOut, Menu } from 'lucide-react';
import { useAuth } from '@/contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
import DashboardContainer from '../dashboard/DashboardContainer';
import { cn } from '@/lib/utils';
const Main: React.FC = () => {
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { HeaderProvider, useHeader } from '@/contexts/HeaderContext';
const MainShell: React.FC = () => {
const { header, resetHeader } = useHeader();
useEffect(() => {
return () => {
resetHeader();
};
}, [resetHeader]);
const { user, logout } = useAuth();
const navigate = useNavigate();
const [hasActiveEvent, setHasActiveEvent] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// header is already available above
// Check for active events - This will be replaced with actual API call
useEffect(() => {
@ -54,26 +64,45 @@ const Main: React.FC = () => {
<header className="sticky top-0 z-40 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className="md:hidden"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle menu</span>
</Button>
<div
<div
className="flex items-center gap-2 cursor-pointer"
onClick={() => navigate('/')}
>
<img
src="/Logo.png"
alt="Logo"
className="h-10 w-auto object-contain"
<img
src="/icon.png"
alt="Logo"
className="h-9 w-9 object-contain"
/>
{!header.left && (
<img
src="/Logo.png"
alt="Logo"
className="hidden md:block h-10 w-auto object-contain"
/>
)}
</div>
{header.mobileMenu ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="md:hidden" aria-label="Menü">
<Menu className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">{header.mobileMenu}</DropdownMenuContent>
</DropdownMenu>
) : null}
{header.left ? (
<div className="hidden md:flex items-center gap-2">{header.left}</div>
) : null}
</div>
{header.title && (
<div className="absolute left-1/2 -translate-x-1/2 text-center text-sm font-medium">
{header.title}
</div>
)}
<div className="flex items-center gap-2">
<div className="hidden md:block text-sm text-muted-foreground">
@ -105,4 +134,12 @@ const Main: React.FC = () => {
);
};
const Main: React.FC = () => {
return (
<HeaderProvider>
<MainShell />
</HeaderProvider>
);
};
export default Main;

View File

@ -0,0 +1,43 @@
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
type HeaderState = {
title: string | null;
left: React.ReactNode | null;
mobileMenu: React.ReactNode | null;
};
type HeaderContextValue = {
header: HeaderState;
setHeader: (next: Partial<HeaderState>) => void;
resetHeader: () => void;
};
const DEFAULT_HEADER: HeaderState = {
title: null,
left: null,
mobileMenu: null,
};
const HeaderContext = createContext<HeaderContextValue | null>(null);
export const HeaderProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [header, setHeaderState] = useState<HeaderState>(DEFAULT_HEADER);
const setHeader = useCallback((next: Partial<HeaderState>) => {
setHeaderState((prev) => ({ ...prev, ...next }));
}, []);
const resetHeader = useCallback(() => {
setHeaderState(DEFAULT_HEADER);
}, []);
const value = useMemo(() => ({ header, setHeader, resetHeader }), [header, setHeader, resetHeader]);
return <HeaderContext.Provider value={value}>{children}</HeaderContext.Provider>;
};
export const useHeader = () => {
const ctx = useContext(HeaderContext);
if (!ctx) throw new Error('useHeader must be used within HeaderProvider');
return ctx;
};

View File

@ -564,9 +564,6 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
{isMobile ? (
<div className="px-3 py-2">
<div className="flex items-center justify-between gap-2">
<div className="text-sm font-medium">
{mode === 'edit' ? 'Event bearbeiten' : 'Event erstellen'}
</div>
<div className="text-xs text-muted-foreground">{loadingInitial ? 'Lade…' : ''}</div>
</div>
{error && <div className="text-sm text-destructive pt-1">{error}</div>}

View File

@ -1,25 +1,80 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
import { type Event } from '@/features/event/api/eventService';
import { ArrowLeft, LayoutGrid } from 'lucide-react';
import { useHeader } from '@/contexts/HeaderContext';
import EventFormInline from './EventFormInline';
interface EventWizardProps {
onComplete: () => void;
onBackToOverview: () => void;
events: Event[];
loading: boolean;
error: string | null;
onReload: () => void;
}
const EventWizard: React.FC<EventWizardProps> = ({ onComplete, events, loading, error, onReload }) => {
const EventWizard: React.FC<EventWizardProps> = ({ onComplete, onBackToOverview, events, loading, error, onReload }) => {
const [showForm, setShowForm] = useState(false);
const [activeEventId, setActiveEventId] = useState<string | number | null>(null);
const { setHeader, resetHeader } = useHeader();
const canGoTimeline = showForm;
const handleBackToTimeline = () => {
setShowForm(false);
setActiveEventId(null);
};
useEffect(() => {
onReload();
}, [onReload]);
useEffect(() => {
const title = showForm ? (activeEventId ? 'Event bearbeiten' : 'Event erstellen') : 'Veranstaltungen';
setHeader({
title,
left: (
<>
<Button type="button" variant="ghost" size="sm" onClick={onBackToOverview} className="h-10 px-2">
<span className="flex flex-col items-center leading-tight">
<LayoutGrid className="h-4 w-4" />
<span className="text-[10px] mt-0.5">Übersicht</span>
</span>
</Button>
{showForm && (
<Button type="button" variant="ghost" size="sm" onClick={handleBackToTimeline} className="h-10 px-2">
<span className="flex flex-col items-center leading-tight">
<ArrowLeft className="h-4 w-4" />
<span className="text-[10px] mt-0.5">Timeline</span>
</span>
</Button>
)}
</>
),
mobileMenu: (
<>
<DropdownMenuItem onClick={onBackToOverview} className="gap-2">
<LayoutGrid className="h-4 w-4" />
<span>Zurück zur Übersicht</span>
</DropdownMenuItem>
{showForm && (
<DropdownMenuItem onClick={handleBackToTimeline} className="gap-2">
<ArrowLeft className="h-4 w-4" />
<span>Zurück zur Timeline</span>
</DropdownMenuItem>
)}
</>
),
});
return () => {
resetHeader();
};
}, [activeEventId, onBackToOverview, resetHeader, setHeader, showForm]);
const sortedEvents = useMemo(() => {
if (!events.length) return [] as Event[];
return [...events].sort((a, b) => {
@ -71,23 +126,9 @@ const EventWizard: React.FC<EventWizardProps> = ({ onComplete, events, loading,
return (
<Card className="w-full mx-auto md:max-w-5xl">
<CardHeader>
<CardTitle>Veranstaltungen</CardTitle>
</CardHeader>
<CardContent>
{showForm ? (
<div className="space-y-4">
<div className="flex items-center justify-between">
<Button
variant="ghost"
onClick={() => {
setShowForm(false);
setActiveEventId(null);
}}
>
Zurück zur Timeline
</Button>
</div>
<EventFormInline
mode={activeEventId ? 'edit' : 'create'}
eventId={activeEventId}

View File

@ -6,9 +6,10 @@ import { toast } from 'sonner';
interface EventWizardContainerProps {
onComplete: () => void;
onBackToOverview: () => void;
}
const EventWizardContainer: React.FC<EventWizardContainerProps> = ({ onComplete }) => {
const EventWizardContainer: React.FC<EventWizardContainerProps> = ({ onComplete, onBackToOverview }) => {
const apiFetch = useApiFetch();
const [events, setEvents] = useState<Event[]>([]);
const [loading, setLoading] = useState(false);
@ -32,6 +33,7 @@ const EventWizardContainer: React.FC<EventWizardContainerProps> = ({ onComplete
return (
<EventWizard
onComplete={onComplete}
onBackToOverview={onBackToOverview}
events={events}
loading={loading}
error={error}

View File

@ -47,7 +47,7 @@ describe('EventWizardContainer (integration)', () => {
const { Wrapper } = renderWithProviders(<div />);
render(
<EventWizardContainer onComplete={() => {}} />,
<EventWizardContainer onComplete={() => {}} onBackToOverview={() => {}} />,
{ wrapper: Wrapper as any }
);
@ -61,7 +61,7 @@ describe('EventWizardContainer (integration)', () => {
const { Wrapper } = renderWithProviders(<div />);
render(
<EventWizardContainer onComplete={() => {}} />,
<EventWizardContainer onComplete={() => {}} onBackToOverview={() => {}} />,
{ wrapper: Wrapper as any }
);

View File

@ -1,6 +1,4 @@
import React from 'react';
import { ArrowLeft } from 'lucide-react';
import { Button } from '@/components/ui/button';
import EventWizardContainer from '@/features/event/containers/EventWizardContainer';
import { type DashboardModule } from '@/components/dashboard/dashboardModuleTypes';
@ -10,16 +8,6 @@ export const eventDashboardModule: DashboardModule = {
getDescription: () => 'Verwalte deine Veranstaltungen',
getIllustration: () => '/images/robo/robo_event.png',
renderWizard: (_ctx, onBack) => (
<div className="space-y-6">
<Button
variant="ghost"
className="mb-4"
onClick={onBack}
>
<ArrowLeft className="mr-2 h-4 w-4" />
Zurück zur Übersicht
</Button>
<EventWizardContainer onComplete={onBack} />
</div>
<EventWizardContainer onComplete={onBack} onBackToOverview={onBack} />
),
};