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 { 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,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">
|
||||
<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
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
onClick={() => navigate('/')}
|
||||
>
|
||||
<img
|
||||
src="/Logo.png"
|
||||
src="/icon.png"
|
||||
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>
|
||||
|
||||
{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">
|
||||
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;
|
||||
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 ? (
|
||||
<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>}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 }
|
||||
);
|
||||
|
||||
|
||||
@ -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} />
|
||||
),
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user