new desighn finale Test1
#deploy
This commit is contained in:
parent
8377e822b9
commit
26fcadc747
BIN
public/Logo.png
BIN
public/Logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 17 KiB |
BIN
public/LogoFull.png
Normal file
BIN
public/LogoFull.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@ -4,12 +4,22 @@ import { LogOut, Menu } from 'lucide-react';
|
|||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import DashboardContainer from '../dashboard/DashboardContainer';
|
import DashboardContainer from '../dashboard/DashboardContainer';
|
||||||
import { cn } from '@/lib/utils';
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||||
const Main: React.FC = () => {
|
import { HeaderProvider, useHeader } from '@/contexts/HeaderContext';
|
||||||
|
|
||||||
|
const MainShell: React.FC = () => {
|
||||||
|
const { header, resetHeader } = useHeader();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
resetHeader();
|
||||||
|
};
|
||||||
|
}, [resetHeader]);
|
||||||
|
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [hasActiveEvent, setHasActiveEvent] = useState(false);
|
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
|
// Check for active events - This will be replaced with actual API call
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -54,27 +64,46 @@ const Main: React.FC = () => {
|
|||||||
<header className="sticky top-0 z-40 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<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="container flex h-16 items-center justify-between px-4">
|
||||||
<div className="flex items-center gap-2">
|
<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"
|
className="flex items-center gap-2 cursor-pointer"
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/Logo.png"
|
src="/icon.png"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
className="h-10 w-auto object-contain"
|
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>
|
</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>
|
</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="flex items-center gap-2">
|
||||||
<div className="hidden md:block text-sm text-muted-foreground">
|
<div className="hidden md:block text-sm text-muted-foreground">
|
||||||
Hallo, {user?.name?.split(' ')[0] || 'Benutzer'}
|
Hallo, {user?.name?.split(' ')[0] || 'Benutzer'}
|
||||||
@ -105,4 +134,12 @@ const Main: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Main: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<HeaderProvider>
|
||||||
|
<MainShell />
|
||||||
|
</HeaderProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Main;
|
export default Main;
|
||||||
43
src/contexts/HeaderContext.tsx
Normal file
43
src/contexts/HeaderContext.tsx
Normal 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;
|
||||||
|
};
|
||||||
@ -564,9 +564,6 @@ const EventFormInline: React.FC<EventFormInlineProps> = ({ onSuccess, onCancel,
|
|||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<div className="px-3 py-2">
|
<div className="px-3 py-2">
|
||||||
<div className="flex items-center justify-between gap-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 className="text-xs text-muted-foreground">{loadingInitial ? 'Lade…' : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
{error && <div className="text-sm text-destructive pt-1">{error}</div>}
|
{error && <div className="text-sm text-destructive pt-1">{error}</div>}
|
||||||
|
|||||||
@ -1,25 +1,80 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
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 { Button } from "@/components/ui/button";
|
||||||
|
import { DropdownMenuItem } from '@/components/ui/dropdown-menu';
|
||||||
import { type Event } from '@/features/event/api/eventService';
|
import { type Event } from '@/features/event/api/eventService';
|
||||||
|
import { ArrowLeft, LayoutGrid } from 'lucide-react';
|
||||||
|
import { useHeader } from '@/contexts/HeaderContext';
|
||||||
import EventFormInline from './EventFormInline';
|
import EventFormInline from './EventFormInline';
|
||||||
|
|
||||||
interface EventWizardProps {
|
interface EventWizardProps {
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
|
onBackToOverview: () => void;
|
||||||
events: Event[];
|
events: Event[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
onReload: () => void;
|
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 [showForm, setShowForm] = useState(false);
|
||||||
const [activeEventId, setActiveEventId] = useState<string | number | null>(null);
|
const [activeEventId, setActiveEventId] = useState<string | number | null>(null);
|
||||||
|
const { setHeader, resetHeader } = useHeader();
|
||||||
|
|
||||||
|
const canGoTimeline = showForm;
|
||||||
|
const handleBackToTimeline = () => {
|
||||||
|
setShowForm(false);
|
||||||
|
setActiveEventId(null);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onReload();
|
onReload();
|
||||||
}, [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(() => {
|
const sortedEvents = useMemo(() => {
|
||||||
if (!events.length) return [] as Event[];
|
if (!events.length) return [] as Event[];
|
||||||
return [...events].sort((a, b) => {
|
return [...events].sort((a, b) => {
|
||||||
@ -71,23 +126,9 @@ const EventWizard: React.FC<EventWizardProps> = ({ onComplete, events, loading,
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full mx-auto md:max-w-5xl">
|
<Card className="w-full mx-auto md:max-w-5xl">
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Veranstaltungen</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{showForm ? (
|
{showForm ? (
|
||||||
<div className="space-y-4">
|
<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
|
<EventFormInline
|
||||||
mode={activeEventId ? 'edit' : 'create'}
|
mode={activeEventId ? 'edit' : 'create'}
|
||||||
eventId={activeEventId}
|
eventId={activeEventId}
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import { toast } from 'sonner';
|
|||||||
|
|
||||||
interface EventWizardContainerProps {
|
interface EventWizardContainerProps {
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
|
onBackToOverview: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventWizardContainer: React.FC<EventWizardContainerProps> = ({ onComplete }) => {
|
const EventWizardContainer: React.FC<EventWizardContainerProps> = ({ onComplete, onBackToOverview }) => {
|
||||||
const apiFetch = useApiFetch();
|
const apiFetch = useApiFetch();
|
||||||
const [events, setEvents] = useState<Event[]>([]);
|
const [events, setEvents] = useState<Event[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -32,6 +33,7 @@ const EventWizardContainer: React.FC<EventWizardContainerProps> = ({ onComplete
|
|||||||
return (
|
return (
|
||||||
<EventWizard
|
<EventWizard
|
||||||
onComplete={onComplete}
|
onComplete={onComplete}
|
||||||
|
onBackToOverview={onBackToOverview}
|
||||||
events={events}
|
events={events}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ describe('EventWizardContainer (integration)', () => {
|
|||||||
|
|
||||||
const { Wrapper } = renderWithProviders(<div />);
|
const { Wrapper } = renderWithProviders(<div />);
|
||||||
render(
|
render(
|
||||||
<EventWizardContainer onComplete={() => {}} />,
|
<EventWizardContainer onComplete={() => {}} onBackToOverview={() => {}} />,
|
||||||
{ wrapper: Wrapper as any }
|
{ wrapper: Wrapper as any }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ describe('EventWizardContainer (integration)', () => {
|
|||||||
|
|
||||||
const { Wrapper } = renderWithProviders(<div />);
|
const { Wrapper } = renderWithProviders(<div />);
|
||||||
render(
|
render(
|
||||||
<EventWizardContainer onComplete={() => {}} />,
|
<EventWizardContainer onComplete={() => {}} onBackToOverview={() => {}} />,
|
||||||
{ wrapper: Wrapper as any }
|
{ wrapper: Wrapper as any }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ArrowLeft } from 'lucide-react';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import EventWizardContainer from '@/features/event/containers/EventWizardContainer';
|
import EventWizardContainer from '@/features/event/containers/EventWizardContainer';
|
||||||
import { type DashboardModule } from '@/components/dashboard/dashboardModuleTypes';
|
import { type DashboardModule } from '@/components/dashboard/dashboardModuleTypes';
|
||||||
|
|
||||||
@ -10,16 +8,6 @@ export const eventDashboardModule: DashboardModule = {
|
|||||||
getDescription: () => 'Verwalte deine Veranstaltungen',
|
getDescription: () => 'Verwalte deine Veranstaltungen',
|
||||||
getIllustration: () => '/images/robo/robo_event.png',
|
getIllustration: () => '/images/robo/robo_event.png',
|
||||||
renderWizard: (_ctx, onBack) => (
|
renderWizard: (_ctx, onBack) => (
|
||||||
<div className="space-y-6">
|
<EventWizardContainer onComplete={onBack} onBackToOverview={onBack} />
|
||||||
<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>
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user