192 lines
6.7 KiB
TypeScript
192 lines
6.7 KiB
TypeScript
import React from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Link } from 'react-router-dom';
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
|
|
import AppearanceToggleDropdown from '@/components/appearance-dropdown';
|
|
import { Settings, ChevronDown, User } from 'lucide-react';
|
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
|
import { useEventData } from '../hooks/useEventData';
|
|
import { usePollStats } from '../polling/usePollStats';
|
|
|
|
export default function Header({ slug, title = '' }: { slug?: string; title?: string }) {
|
|
if (!slug) {
|
|
return (
|
|
<div className="sticky top-0 z-20 flex items-center justify-between border-b bg-white/70 px-4 py-2 backdrop-blur dark:bg-black/40">
|
|
<div className="font-semibold">{title}</div>
|
|
<div className="flex items-center gap-2">
|
|
<AppearanceToggleDropdown />
|
|
<SettingsSheet />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const { event, loading: eventLoading, error: eventError } = useEventData();
|
|
const stats = usePollStats(slug);
|
|
|
|
if (eventLoading) {
|
|
return (
|
|
<div className="sticky top-0 z-20 flex items-center justify-between border-b bg-white/70 px-4 py-2 backdrop-blur dark:bg-black/40">
|
|
<div className="font-semibold">Lade Event...</div>
|
|
<div className="flex items-center gap-2">
|
|
<AppearanceToggleDropdown />
|
|
<SettingsSheet />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (eventError || !event) {
|
|
return (
|
|
<div className="sticky top-0 z-20 flex items-center justify-between border-b bg-white/70 px-4 py-2 backdrop-blur dark:bg-black/40">
|
|
<div className="font-semibold text-red-600">Event nicht gefunden</div>
|
|
<div className="flex items-center gap-2">
|
|
<AppearanceToggleDropdown />
|
|
<SettingsSheet />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Get event icon or generate initials
|
|
const getEventAvatar = (event: any) => {
|
|
if (event.type?.icon) {
|
|
return (
|
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-pink-100 text-pink-600 text-xl">
|
|
{event.type.icon}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Fallback to initials
|
|
const getInitials = (name: string) => {
|
|
const words = name.split(' ');
|
|
if (words.length >= 2) {
|
|
return `${words[0][0]}${words[1][0]}`.toUpperCase();
|
|
}
|
|
return name.substring(0, 2).toUpperCase();
|
|
};
|
|
|
|
return (
|
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-pink-100 text-pink-600 font-semibold text-sm">
|
|
{getInitials(event.name)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="sticky top-0 z-20 flex items-center justify-between border-b bg-white/70 px-4 py-2 backdrop-blur dark:bg-black/40">
|
|
<div className="flex items-center gap-3">
|
|
{getEventAvatar(event)}
|
|
<div className="flex flex-col">
|
|
<div className="font-semibold text-base">{event.name}</div>
|
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
{stats && (
|
|
<>
|
|
<span className="flex items-center gap-1">
|
|
<User className="h-3 w-3" />
|
|
<span>{stats.onlineGuests} online</span>
|
|
</span>
|
|
<span>•</span>
|
|
<span className="flex items-center gap-1">
|
|
<span className="font-medium">{stats.tasksSolved}</span> Aufgaben gelöst
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<AppearanceToggleDropdown />
|
|
<SettingsSheet />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SettingsSheet() {
|
|
return (
|
|
<Sheet>
|
|
<SheetTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="h-9 w-9 rounded-md">
|
|
<Settings className="h-5 w-5" />
|
|
<span className="sr-only">Einstellungen öffnen</span>
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent side="right" className="w-80 sm:w-96">
|
|
<SheetHeader>
|
|
<SheetTitle>Einstellungen</SheetTitle>
|
|
</SheetHeader>
|
|
<div className="mt-4 space-y-4">
|
|
<Collapsible defaultOpen>
|
|
<div className="flex items-center justify-between">
|
|
<div className="text-sm font-medium">Cache</div>
|
|
<CollapsibleTrigger asChild>
|
|
<Button variant="ghost" size="sm" className="h-7 px-2">
|
|
<ChevronDown className="h-4 w-4" />
|
|
</Button>
|
|
</CollapsibleTrigger>
|
|
</div>
|
|
<CollapsibleContent>
|
|
<div className="mt-2">
|
|
<ClearCacheButton />
|
|
</div>
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
|
|
<Collapsible defaultOpen>
|
|
<div className="flex items-center justify-between">
|
|
<div className="text-sm font-medium">Rechtliches</div>
|
|
<CollapsibleTrigger asChild>
|
|
<Button variant="ghost" size="sm" className="h-7 px-2">
|
|
<ChevronDown className="h-4 w-4" />
|
|
</Button>
|
|
</CollapsibleTrigger>
|
|
</div>
|
|
<CollapsibleContent>
|
|
<ul className="mt-2 list-disc pl-5 text-sm">
|
|
<li><Link to="/legal/impressum" className="underline">Impressum</Link></li>
|
|
<li><Link to="/legal/datenschutz" className="underline">Datenschutz</Link></li>
|
|
<li><Link to="/legal/agb" className="underline">AGB</Link></li>
|
|
</ul>
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
);
|
|
}
|
|
|
|
function ClearCacheButton() {
|
|
const [busy, setBusy] = React.useState(false);
|
|
const [done, setDone] = React.useState(false);
|
|
|
|
async function clearAll() {
|
|
setBusy(true); setDone(false);
|
|
try {
|
|
// Clear CacheStorage
|
|
if ('caches' in window) {
|
|
const keys = await caches.keys();
|
|
await Promise.all(keys.map((k) => caches.delete(k)));
|
|
}
|
|
// Clear known IndexedDB dbs (best-effort)
|
|
if ('indexedDB' in window) {
|
|
try { await new Promise((res, rej) => { const r = indexedDB.deleteDatabase('upload-queue'); r.onsuccess=()=>res(null); r.onerror=()=>res(null); }); } catch {}
|
|
}
|
|
setDone(true);
|
|
} finally {
|
|
setBusy(false);
|
|
setTimeout(() => setDone(false), 2500);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="mt-2">
|
|
<Button variant="secondary" onClick={clearAll} disabled={busy}>
|
|
{busy ? 'Leere Cache…' : 'Cache leeren'}
|
|
</Button>
|
|
{done && <div className="mt-2 text-xs text-muted-foreground">Cache gelöscht.</div>}
|
|
</div>
|
|
);
|
|
}
|