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) => (
#{index + 1}
{entry.guest || 'Gast'}
{entry.photos} Fotos
{entry.likes} Likes
))}
)}
);
}
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 ? (
) : (
)}
{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 ? (
) : (
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({ slug }: { slug: string }) {
return (
Neues Foto hochladen
Aufgabe ziehen
);
}
export default function AchievementsPage() {
const { slug } = useParams<{ slug: 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 (!slug) return;
const controller = new AbortController();
setLoading(true);
setError(null);
fetchAchievements(slug, 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();
}, [slug, personalName]);
const hasPersonal = Boolean(data?.personal);
if (!slug) {
return null;
}
return (
Erfolge
Behalte deine Highlights, Badges und die aktivsten Gaeste im Blick.
{loading && (
)}
{!loading && error && (
{error}
setActiveTab(hasPersonal ? 'personal' : 'event')}>
Erneut versuchen
)}
{!loading && !error && data && (
<>
setActiveTab('personal')}
disabled={!hasPersonal}
className="flex items-center gap-2"
>
Meine Erfolge
setActiveTab('event')}
className="flex items-center gap-2"
>
Event Highlights
setActiveTab('feed')}
className="flex items-center gap-2"
>
Live Feed
{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' &&
}
>
)}
);
}