import React, { useEffect, useMemo, useState } from 'react'; import { Link, useParams } from 'react-router-dom'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Separator } from '@/components/ui/separator'; import { AchievementBadge, AchievementsPayload, FeedEntry, LeaderboardEntry, TimelinePoint, TopPhotoHighlight, TrendingEmotionHighlight, fetchAchievements, } from '../services/achievementApi'; import { useGuestIdentity } from '../context/GuestIdentityContext'; import { Sparkles, Award, Trophy, Camera, Users, BarChart2, Flame } from 'lucide-react'; function formatNumber(value: number): string { return new Intl.NumberFormat('de-DE').format(value); } function formatRelativeTime(input: string): string { const date = new Date(input); if (Number.isNaN(date.getTime())) return ''; const diff = Date.now() - date.getTime(); const minute = 60_000; const hour = 60 * minute; const day = 24 * hour; if (diff < minute) return 'gerade eben'; if (diff < hour) { const minutes = Math.round(diff / minute); return `vor ${minutes} Min`; } if (diff < day) { const hours = Math.round(diff / hour); return `vor ${hours} Std`; } const days = Math.round(diff / day); return `vor ${days} Tagen`; } function badgeVariant(earned: boolean): string { return earned ? 'bg-emerald-500/10 text-emerald-500 border border-emerald-500/30' : 'bg-muted text-muted-foreground'; } function Leaderboard({ title, icon: Icon, entries, emptyCopy }: { title: string; icon: React.ElementType; entries: LeaderboardEntry[]; emptyCopy: string }) { return (
{title} Top 5 Teilnehmer dieses Events
{entries.length === 0 ? (

{emptyCopy}

) : (
    {entries.map((entry, index) => (
  1. #{index + 1} {entry.guest || 'Gast'}
    {entry.photos} Fotos {entry.likes} Likes
  2. ))}
)}
); } function BadgesGrid({ badges }: { badges: AchievementBadge[] }) { if (badges.length === 0) { return ( Badges Erfuelle Aufgaben und sammle Likes, um Badges freizuschalten.

Noch keine Badges verfuegbar.

); } return ( Badges Dein Fortschritt bei den verfuegbaren Erfolgen. {badges.map((badge) => (

{badge.title}

{badge.description}

{badge.earned ? 'Abgeschlossen' : `Fortschritt: ${badge.progress}/${badge.target}`}
))}
); } function Timeline({ points }: { points: TimelinePoint[] }) { if (points.length === 0) { return null; } return ( Timeline Wie das Event im Laufe der Zeit Fahrt aufgenommen hat. {points.map((point) => (
{point.date} {point.photos} Fotos | {point.guests} Gaeste
))}
); } function Feed({ feed }: { feed: FeedEntry[] }) { if (feed.length === 0) { return ( Live Feed Neue Uploads erscheinen hier in Echtzeit.

Noch keine Uploads - starte die Kamera und lege los!

); } return ( Live Feed Die neuesten Momente aus deinem Event. {feed.map((item) => (
{item.thumbnail ? ( Vorschau ) : (
)}

{item.guest || 'Gast'}

{item.task &&

Aufgabe: {item.task}

}
{formatRelativeTime(item.createdAt)} {item.likes} Likes
))}
); } function Highlights({ topPhoto, trendingEmotion }: { topPhoto: TopPhotoHighlight | null; trendingEmotion: TrendingEmotionHighlight | null }) { if (!topPhoto && !trendingEmotion) { return null; } return (
{topPhoto && (
Publikumsliebling Das Foto mit den meisten Likes.
{topPhoto.thumbnail ? ( Top Foto ) : (
Kein Vorschau-Bild
)}

{topPhoto.guest || 'Gast'} � {topPhoto.likes} Likes

{topPhoto.task &&

Aufgabe: {topPhoto.task}

}

{formatRelativeTime(topPhoto.createdAt)}

)} {trendingEmotion && (
Trend-Emotion Diese Stimmung taucht gerade besonders oft auf.

{trendingEmotion.name}

{trendingEmotion.count} Fotos mit dieser Stimmung

)}
); } function SummaryCards({ data }: { data: AchievementsPayload }) { return (
Fotos gesamt {formatNumber(data.summary.totalPhotos)} Aktive Gaeste {formatNumber(data.summary.uniqueGuests)} Erfuellte Aufgaben {formatNumber(data.summary.tasksSolved)} Likes insgesamt {formatNumber(data.summary.likesTotal)}
); } function PersonalActions({ token }: { token: string }) { return (
); } export default function AchievementsPage() { const { token } = useParams<{ token: string }>(); const identity = useGuestIdentity(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState<'personal' | 'event' | 'feed'>('personal'); const personalName = identity.hydrated && identity.name ? identity.name : undefined; useEffect(() => { if (!token) return; const controller = new AbortController(); setLoading(true); setError(null); fetchAchievements(token, personalName, controller.signal) .then((payload) => { setData(payload); if (!payload.personal) { setActiveTab('event'); } }) .catch((err) => { if (err.name === 'AbortError') return; console.error('Failed to load achievements', err); setError(err.message || 'Erfolge konnten nicht geladen werden.'); }) .finally(() => setLoading(false)); return () => controller.abort(); }, [token, personalName]); const hasPersonal = Boolean(data?.personal); if (!token) { return null; } return (

Erfolge

Behalte deine Highlights, Badges und die aktivsten Gaeste im Blick.

{loading && (
)} {!loading && error && ( {error} )} {!loading && !error && data && ( <>
{activeTab === 'personal' && hasPersonal && data.personal && (
Hi {data.personal.guestName || identity.name || 'Gast'}! {data.personal.photos} Fotos | {data.personal.tasks} Aufgaben | {data.personal.likes} Likes
)} {activeTab === 'event' && (
)} {activeTab === 'feed' && } )}
); }