import { getPersonHistory, type PersonMini } from "@/apis/personHistoryApi"; import { getShowById } from "@/apis/showApi"; import styles from "@/app/stackStyles/participantStyles"; import { useShowContext } from "@/contexts/ShowContext"; import Ionicons from "@expo/vector-icons/Ionicons"; import { router, useLocalSearchParams } from "expo-router"; import * as WebBrowser from "expo-web-browser"; import React from "react"; import { Image, Text, TouchableOpacity, View } from "react-native"; import { GestureHandlerRootView, ScrollView, } from "react-native-gesture-handler"; type SeasonEntry = { seasonNumber: number; partner: PersonMini | null; participants: PersonMini[]; startDate: string | null; }; type AppearanceGroup = { show: { id: number; title: string; bannerUri: string; thumbnailUri: string; }; seasons: SeasonEntry[]; }; export default function ParticipantScreen() { const { shows } = useShowContext(); const { name, participantId } = useLocalSearchParams(); const pid = Array.isArray(participantId) ? Number(participantId[0]) : Number(participantId); const [, setLoading] = React.useState(false); const [, setError] = React.useState(null); const [appearances, setAppearances] = React.useState([]); const formatYear = (iso?: string | null) => { if (!iso) return null; const [y] = iso.split("-"); return y || null; }; React.useEffect(() => { if (!pid || Number.isNaN(pid)) return; const controller = new AbortController(); setLoading(true); setError(null); (async () => { try { const hist = await getPersonHistory(pid, controller.signal); const grouped = new Map>(); for (const h of hist) { if (!Number.isFinite(h.showId) || h.showId <= 0) continue; const seasonsForShow = grouped.get(h.showId) ?? new Map(); const existing = seasonsForShow.get(h.seasonNumber); if (existing) { seasonsForShow.set(h.seasonNumber, { seasonNumber: h.seasonNumber, partner: existing.partner ?? h.partner ?? null, participants: existing.participants.length ? existing.participants : (h.seasonParticipants ?? []), startDate: existing.startDate ?? h.startDate ?? null, }); } else { seasonsForShow.set(h.seasonNumber, { seasonNumber: h.seasonNumber, partner: h.partner ?? null, participants: h.seasonParticipants ?? [], startDate: h.startDate ?? null, }); } grouped.set(h.showId, seasonsForShow); } const showIds = Array.from(grouped.keys()); const fromContext = showIds .map((id) => shows.find((s) => s.id === id)) .filter((s): s is (typeof shows)[number] => !!s); const missingIds = showIds.filter( (id) => !fromContext.some((s) => s.id === id) ); const fetched = await Promise.all( missingIds.map(async (id) => { try { const s = await getShowById(id); return s; } catch { return null; } }) ); const allShows = [ ...fromContext, ...fetched.filter(Boolean), ] as typeof shows; const result: AppearanceGroup[] = allShows.map((s) => { const seasonsMap = grouped.get(s.id)!; const seasonsSorted = Array.from(seasonsMap.values()).sort( (a, b) => a.seasonNumber - b.seasonNumber ); return { show: { id: s.id, title: s.title, bannerUri: s.bannerUri, thumbnailUri: s.thumbnailUri, }, seasons: seasonsSorted, }; }); result.sort((a, b) => a.show.title.localeCompare(b.show.title, "de", { sensitivity: "base", }) ); setAppearances(result); } catch (e: any) { if (!controller.signal.aborted) setError(e?.message || "Fehler beim Laden"); } finally { if (!controller.signal.aborted) setLoading(false); } })(); return () => controller.abort(); }, [pid, shows]); const [expandedShows, setExpandedShows] = React.useState>( new Set() ); const toggleExpand = React.useCallback((showId: number) => { setExpandedShows((prev) => { const next = new Set(prev); if (next.has(showId)) next.delete(showId); else next.add(showId); return next; }); }, []); const goToShow = React.useCallback((id: number) => { router.push({ pathname: "/showDetails", params: { id: String(id) } }); }, []); const goToPerson = React.useCallback( (p: PersonMini) => { if (!p?.personId) return; if (p.personId === pid) return; router.push({ pathname: "/participant", params: { participantId: String(p.personId), name: p.name }, }); }, [pid] ); return ( {name} router.back()} > WebBrowser.openBrowserAsync( "https://www.google.com/search?udm=2&q=" + encodeURIComponent(String(name)) ) } > Auftritte: {appearances.map(({ show, seasons }) => { const partners = Array.from( new Map( seasons .map((s) => s.partner) .filter((p): p is NonNullable => !!p) .map((p) => [p.personId, p]) ).values() ); const allParticipants = Array.from( new Map( seasons .flatMap((s) => s.participants) .filter((p) => p.personId !== pid) .map((p) => [p.personId, p]) ).values() ); const isExpanded = expandedShows.has(show.id); const visible = isExpanded ? allParticipants : allParticipants.slice(0, 12); const restCount = Math.max( allParticipants.length - visible.length, 0 ); return ( goToShow(show.id)} > {show.title} ({formatYear(seasons[0]?.startDate)}) Staffel {seasons.map((s) => s.seasonNumber).join(" und ")} Weitere Teilnehmer {visible.map((p) => ( goToPerson(p)} > {p.name} ))} {!isExpanded && restCount > 0 && ( toggleExpand(show.id)} style={styles.moreChip} > +{restCount} mehr )} {isExpanded && allParticipants.length > 12 && ( toggleExpand(show.id)} style={styles.moreChip} > Weniger )} {partners.length > 0 && ( <> Partner {partners.map((p) => ( {p.name} ))} )} ); })} ); }