api: reconfigered api handling
This commit is contained in:
@@ -16,7 +16,8 @@ export async function getAutoComplete(
|
|||||||
const url = `${DISCOVER_BASE}/autoComplete?q=${encodeURIComponent(
|
const url = `${DISCOVER_BASE}/autoComplete?q=${encodeURIComponent(
|
||||||
query
|
query
|
||||||
)}&limit=${limit}`;
|
)}&limit=${limit}`;
|
||||||
const res = await fetch(url, { signal });
|
const apiKey = process.env.EXPO_PUBLIC_API_KEY;
|
||||||
|
const res = await fetch(url, { signal, headers: { 'Content-Type': 'application/json', "X-API-Key": apiKey ?? "", } });
|
||||||
if (!res.ok) throw new Error("AutoComplete failed " + res.status);
|
if (!res.ok) throw new Error("AutoComplete failed " + res.status);
|
||||||
const data: unknown = await res.json();
|
const data: unknown = await res.json();
|
||||||
if (!Array.isArray(data)) return [];
|
if (!Array.isArray(data)) return [];
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
export type PersonHistorySeasonRaw = {
|
|
||||||
seasonId: number;
|
|
||||||
show: number;
|
|
||||||
seasonNumber: number;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string | null;
|
|
||||||
seasonParticipants: any[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PersonHistoryEntry = {
|
|
||||||
showId: number;
|
|
||||||
seasonId: number;
|
|
||||||
seasonNumber: number;
|
|
||||||
partner?: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
birthDate?: string;
|
|
||||||
imageUri: string | null;
|
|
||||||
} | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PERSON_API_BASE = "http://45.157.177.99:8080/persons";
|
|
||||||
|
|
||||||
export async function getPersonHistory(
|
|
||||||
personId: number
|
|
||||||
): Promise<PersonHistoryEntry[]> {
|
|
||||||
const url = `${PERSON_API_BASE}/${personId}/history`;
|
|
||||||
try {
|
|
||||||
console.log("[getPersonHistory] Fetch:", url);
|
|
||||||
const res = await fetch(url);
|
|
||||||
if (!res.ok) throw new Error("History fetch failed " + res.status);
|
|
||||||
const data: unknown = await res.json();
|
|
||||||
if (!Array.isArray(data)) {
|
|
||||||
console.warn("History expected array, got:", data);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return (data as PersonHistorySeasonRaw[]).map((season) => {
|
|
||||||
const me = season.seasonParticipants.find(
|
|
||||||
(p) => p.person.personId === personId
|
|
||||||
);
|
|
||||||
let partner: PersonHistoryEntry["partner"] = null;
|
|
||||||
if (me?.partner?.person) {
|
|
||||||
partner = {
|
|
||||||
id: me.partner.person.personId,
|
|
||||||
name: me.partner.person.name,
|
|
||||||
imageUri: me.partner.person.imageUrl ?? null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
showId: season.show,
|
|
||||||
seasonId: season.seasonId,
|
|
||||||
seasonNumber: season.seasonNumber,
|
|
||||||
partner,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error("getPersonHistory error:", e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,8 @@ export async function getSearchResults(
|
|||||||
filteredTags.join(",")
|
filteredTags.join(",")
|
||||||
)}&limit=${limit}`;
|
)}&limit=${limit}`;
|
||||||
|
|
||||||
const res = await fetch(url, { signal });
|
const apiKey = process.env.EXPO_PUBLIC_API_KEY;
|
||||||
|
const res = await fetch(url, { signal, headers: { 'Content-Type': 'application/json', "X-API-Key": apiKey ?? "", } });
|
||||||
if (!res.ok) throw new Error("AutoComplete failed " + res.status);
|
if (!res.ok) throw new Error("AutoComplete failed " + res.status);
|
||||||
|
|
||||||
const data: unknown = await res.json();
|
const data: unknown = await res.json();
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
|
|
||||||
export type RawSeasonParticipant = {
|
export type RawSeasonParticipant = {
|
||||||
id: { seasonId: number; personId: number };
|
|
||||||
person: {
|
|
||||||
personId: number;
|
personId: number;
|
||||||
name: string;
|
name: string;
|
||||||
birthDate: string;
|
birthDate?: string;
|
||||||
imageUrl: string | null;
|
imageUrl?: string | null;
|
||||||
|
partner?: {
|
||||||
|
personId: number;
|
||||||
|
name: string;
|
||||||
|
birthDate?: string;
|
||||||
|
imageUrl?: string | null;
|
||||||
};
|
};
|
||||||
partner: unknown | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RawSeason = {
|
export type RawSeason = {
|
||||||
seasonId: number;
|
seasonId: number;
|
||||||
show: number;
|
|
||||||
seasonNumber: number;
|
seasonNumber: number;
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string | null;
|
endDate?: string | null;
|
||||||
moderators: unknown[];
|
|
||||||
seasonParticipants: RawSeasonParticipant[];
|
seasonParticipants: RawSeasonParticipant[];
|
||||||
|
show?: number;
|
||||||
|
moderators?: unknown[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SeasonParticipant = {
|
export type SeasonParticipant = {
|
||||||
@@ -43,27 +46,37 @@ export async function getSeason(
|
|||||||
): Promise<Season | null> {
|
): Promise<Season | null> {
|
||||||
const url = `${SEASON_BASE_URL}/${showId}/seasons/${seasonNumber}`;
|
const url = `${SEASON_BASE_URL}/${showId}/seasons/${seasonNumber}`;
|
||||||
try {
|
try {
|
||||||
|
const apiKey = process.env.EXPO_PUBLIC_API_KEY;
|
||||||
console.log("[getSeason] Fetch:", url);
|
console.log("[getSeason] Fetch:", url);
|
||||||
const res = await fetch(url);
|
const res = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-API-Key": apiKey ?? "",
|
||||||
|
},
|
||||||
|
});
|
||||||
console.log("[getSeason] Status:", res.status);
|
console.log("[getSeason] Status:", res.status);
|
||||||
if (res.status === 404) return null;
|
if (res.status === 404) return null;
|
||||||
if (!res.ok) throw new Error("Season fetch failed " + res.status);
|
if (!res.ok) throw new Error("Season fetch failed " + res.status);
|
||||||
|
|
||||||
const raw: RawSeason = await res.json();
|
const raw: RawSeason = await res.json();
|
||||||
const participants: SeasonParticipant[] = raw.seasonParticipants.map(
|
|
||||||
(p) => ({
|
const participants: SeasonParticipant[] = Array.isArray(
|
||||||
id: p.person.personId,
|
raw.seasonParticipants
|
||||||
name: p.person.name,
|
)
|
||||||
birthYear: p.person.birthDate
|
? raw.seasonParticipants.map((p) => ({
|
||||||
? Number(p.person.birthDate.slice(0, 4))
|
id: p.personId,
|
||||||
: undefined,
|
name: p.name,
|
||||||
|
birthYear: p.birthDate ? Number(p.birthDate.slice(0, 4)) : undefined,
|
||||||
imageUri:
|
imageUri:
|
||||||
p.person.imageUrl ??
|
p.imageUrl ??
|
||||||
`https://i.pravatar.cc/300?img=${Math.random() * 70}`,
|
`https://i.pravatar.cc/300?img=${Math.floor(Math.random() * 70) + 1}`,
|
||||||
})
|
}))
|
||||||
);
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: raw.seasonId,
|
id: raw.seasonId,
|
||||||
showId: raw.show,
|
showId,
|
||||||
seasonNumber: raw.seasonNumber,
|
seasonNumber: raw.seasonNumber,
|
||||||
startDate: raw.startDate,
|
startDate: raw.startDate,
|
||||||
endDate: raw.endDate,
|
endDate: raw.endDate,
|
||||||
@@ -74,3 +87,4 @@ export async function getSeason(
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ShowProvider } from "@/contexts/ShowContext";
|
|
||||||
import { SeasonProvider } from "@/contexts/SeasonContext";
|
import { SeasonProvider } from "@/contexts/SeasonContext";
|
||||||
|
import { ShowProvider } from "@/contexts/ShowContext";
|
||||||
import { StreamingServiceProvider } from "@/contexts/StreamingServiceContext";
|
import { StreamingServiceProvider } from "@/contexts/StreamingServiceContext";
|
||||||
import { PersonProvider } from "@/contexts/PersonContext";
|
|
||||||
import { DiscoveryProvider } from "@/contexts/DiscoveryContext";
|
import { DiscoveryProvider } from "@/contexts/DiscoveryContext";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import "react-native-reanimated";
|
import "react-native-reanimated";
|
||||||
@@ -11,7 +11,7 @@ export default function RootLayout() {
|
|||||||
<ShowProvider>
|
<ShowProvider>
|
||||||
<SeasonProvider>
|
<SeasonProvider>
|
||||||
<StreamingServiceProvider>
|
<StreamingServiceProvider>
|
||||||
<PersonProvider>
|
|
||||||
<DiscoveryProvider>
|
<DiscoveryProvider>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
@@ -30,7 +30,7 @@ export default function RootLayout() {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</DiscoveryProvider>
|
</DiscoveryProvider>
|
||||||
</PersonProvider>
|
|
||||||
</StreamingServiceProvider>
|
</StreamingServiceProvider>
|
||||||
</SeasonProvider>
|
</SeasonProvider>
|
||||||
</ShowProvider>
|
</ShowProvider>
|
||||||
|
|||||||
@@ -1,47 +1,28 @@
|
|||||||
import { View, Image, Text, TouchableOpacity } from "react-native";
|
|
||||||
import styles from "@/app/stackStyles/participantStyles";
|
import styles from "@/app/stackStyles/participantStyles";
|
||||||
import Ionicons from "@expo/vector-icons/Ionicons";
|
import Ionicons from "@expo/vector-icons/Ionicons";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
|
||||||
import { router, useLocalSearchParams } from "expo-router";
|
import { router, useLocalSearchParams } from "expo-router";
|
||||||
import { usePersonContext } from "@/contexts/PersonContext";
|
import React, { useMemo, useState } from "react";
|
||||||
import * as WebBrowser from "expo-web-browser";
|
import { Image, Text, TouchableOpacity, View } from "react-native";
|
||||||
import {
|
|
||||||
ScrollView,
|
|
||||||
GestureHandlerRootView,
|
|
||||||
} from "react-native-gesture-handler";
|
|
||||||
import { useShowContext } from "@/contexts/ShowContext";
|
import { useShowContext } from "@/contexts/ShowContext";
|
||||||
|
import {
|
||||||
|
GestureHandlerRootView,
|
||||||
|
ScrollView,
|
||||||
|
} from "react-native-gesture-handler";
|
||||||
|
|
||||||
export default function ParticipantScreen() {
|
export default function ParticipantScreen() {
|
||||||
const { getPersonAppearances, isLoading, getError } = usePersonContext();
|
|
||||||
const [appearances, setAppearances] = useState<
|
const [appearances,] = useState<
|
||||||
{
|
{
|
||||||
showId: number;
|
showId: number;
|
||||||
seasons: number[];
|
seasons: number[];
|
||||||
}[]
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
const { shows, error, loading } = useShowContext();
|
const { shows} = useShowContext();
|
||||||
const { participantId, name, season } = useLocalSearchParams();
|
const { name } = useLocalSearchParams();
|
||||||
const numericId = Array.isArray(participantId)
|
|
||||||
? Number(participantId[0])
|
|
||||||
: Number(participantId);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let active = true;
|
|
||||||
(async () => {
|
|
||||||
if (!numericId || Number.isNaN(numericId)) return;
|
|
||||||
const data = await getPersonAppearances(numericId);
|
|
||||||
if (!active) return;
|
|
||||||
const grouped = data.showIds.map((id) => ({
|
|
||||||
showId: id,
|
|
||||||
seasons: data.byShow[id],
|
|
||||||
partners: data.partnersByShow[id] || [],
|
|
||||||
}));
|
|
||||||
setAppearances(grouped as any);
|
|
||||||
})();
|
|
||||||
return () => {
|
|
||||||
active = false;
|
|
||||||
};
|
|
||||||
}, [numericId, getPersonAppearances]);
|
|
||||||
|
|
||||||
const resolved = useMemo(
|
const resolved = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -88,21 +69,9 @@ export default function ParticipantScreen() {
|
|||||||
|
|
||||||
<View style={styles.performedShowsSection}>
|
<View style={styles.performedShowsSection}>
|
||||||
<Text style={styles.performedShowsTitle}>Auftritte:</Text>
|
<Text style={styles.performedShowsTitle}>Auftritte:</Text>
|
||||||
{isLoading(numericId) && (
|
|
||||||
<Text style={{ color: "white", marginTop: 8 }}>Lädt...</Text>
|
|
||||||
)}
|
|
||||||
{getError(numericId) && (
|
|
||||||
<Text style={{ color: "red", marginTop: 8 }}>
|
|
||||||
{getError(numericId)}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{!isLoading(numericId) &&
|
|
||||||
resolved.length === 0 &&
|
|
||||||
!getError(numericId) && (
|
|
||||||
<Text style={{ color: "gray", marginTop: 8 }}>
|
|
||||||
Keine Einträge.
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
horizontal
|
horizontal
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ export default function ShowDetails() {
|
|||||||
genres,
|
genres,
|
||||||
streamingService,
|
streamingService,
|
||||||
id,
|
id,
|
||||||
|
|
||||||
endDate,
|
endDate,
|
||||||
} = useLocalSearchParams();
|
} = useLocalSearchParams();
|
||||||
const [selectedParticipants, setSelectedParticipants] =
|
const [selectedParticipants, setSelectedParticipants] =
|
||||||
React.useState<boolean>(true);
|
React.useState<boolean>(true);
|
||||||
const [selectedSeason, setSelectedSeason] = React.useState<number>(1);
|
const [selectedSeason, setSelectedSeason] = React.useState<number>(1);
|
||||||
const showId = Number(id);
|
const showId = Number(id);
|
||||||
const { fetchSeasonParticipants, fetchSeasonCount } = useSeasonContext();
|
const { fetchSeasonParticipants, fetchSeasonCount, fetchSeasonDates } = useSeasonContext();
|
||||||
const [seasonCount, setSeasonCount] = React.useState<number>(0);
|
const [seasonCount, setSeasonCount] = React.useState<number>(0);
|
||||||
const [participants, setParticipants] = React.useState<
|
const [participants, setParticipants] = React.useState<
|
||||||
{ id: number; name: string; imageUri: string }[]
|
{ id: number; name: string; imageUri: string }[]
|
||||||
@@ -69,8 +68,14 @@ export default function ShowDetails() {
|
|||||||
setPError(null);
|
setPError(null);
|
||||||
setPLoading(true);
|
setPLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await fetchSeasonParticipants(showId, selectedSeason);
|
const [data, dates] = await Promise.all([
|
||||||
if (active) setParticipants(data);
|
fetchSeasonParticipants(showId, selectedSeason),
|
||||||
|
fetchSeasonDates(showId, selectedSeason),
|
||||||
|
]);
|
||||||
|
if (active) {
|
||||||
|
setParticipants(data);
|
||||||
|
setStartDate(dates?.startDate);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (active) setPError("Fehler beim Laden");
|
if (active) setPError("Fehler beim Laden");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -80,14 +85,18 @@ export default function ShowDetails() {
|
|||||||
return () => {
|
return () => {
|
||||||
active = false;
|
active = false;
|
||||||
};
|
};
|
||||||
}, [showId, selectedSeason, fetchSeasonParticipants]);
|
}, [showId, selectedSeason, fetchSeasonParticipants, fetchSeasonDates]);
|
||||||
|
|
||||||
const startDateObj = new Date(startDate as string);
|
const formattedStartDate = React.useMemo(() => {
|
||||||
const formattedStartDate = startDateObj.toLocaleDateString("de-DE", {
|
if (!startDate) return "";
|
||||||
|
const d = new Date(startDate);
|
||||||
|
if (isNaN(d.getTime())) return "";
|
||||||
|
return d.toLocaleDateString("de-DE", {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
});
|
});
|
||||||
|
}, [startDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.mainContainer}>
|
<View style={styles.mainContainer}>
|
||||||
@@ -98,7 +107,10 @@ export default function ShowDetails() {
|
|||||||
paddingBottom: Dimensions.get("window").height * 0.1,
|
paddingBottom: Dimensions.get("window").height * 0.1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
{formattedStartDate ? (
|
||||||
<Text style={styles.startDate}>{formattedStartDate}</Text>
|
<Text style={styles.startDate}>{formattedStartDate}</Text>
|
||||||
|
) : null}
|
||||||
<ShowInfo
|
<ShowInfo
|
||||||
seasons={seasonCount}
|
seasons={seasonCount}
|
||||||
participants={participants.length}
|
participants={participants.length}
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
import React, {
|
|
||||||
createContext,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useState,
|
|
||||||
ReactNode,
|
|
||||||
} from "react";
|
|
||||||
import { getPersonHistory, PersonHistoryEntry } from "@/apis/personApi";
|
|
||||||
|
|
||||||
type PersonAppearances = {
|
|
||||||
raw: PersonHistoryEntry[];
|
|
||||||
byShow: Record<number, number[]>;
|
|
||||||
showIds: number[];
|
|
||||||
partnersByShow: Record<
|
|
||||||
number,
|
|
||||||
{ seasonNumber: number; partner?: PersonHistoryEntry["partner"] }[]
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PersonContextType = {
|
|
||||||
getPersonAppearances: (personId: number) => Promise<PersonAppearances>;
|
|
||||||
getShowIds: (personId: number) => Promise<number[]>;
|
|
||||||
getSeasonsForShow: (personId: number, showId: number) => Promise<number[]>;
|
|
||||||
isLoading: (personId: number) => boolean;
|
|
||||||
getError: (personId: number) => string | null;
|
|
||||||
invalidatePerson: (personId: number) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PersonContext = createContext<PersonContextType | null>(null);
|
|
||||||
|
|
||||||
export const PersonProvider = ({ children }: { children: ReactNode }) => {
|
|
||||||
const [cache, setCache] = useState<Record<number, PersonAppearances>>({});
|
|
||||||
const [loading, setLoading] = useState<Record<number, boolean>>({});
|
|
||||||
const [errors, setErrors] = useState<Record<number, string | null>>({});
|
|
||||||
|
|
||||||
const buildAppearances = (
|
|
||||||
entries: PersonHistoryEntry[]
|
|
||||||
): PersonAppearances => {
|
|
||||||
const byShowSet: Record<number, Set<number>> = {};
|
|
||||||
const partnersByShow: PersonAppearances["partnersByShow"] = {};
|
|
||||||
for (const e of entries) {
|
|
||||||
if (!byShowSet[e.showId]) byShowSet[e.showId] = new Set();
|
|
||||||
byShowSet[e.showId].add(e.seasonNumber);
|
|
||||||
if (!partnersByShow[e.showId]) partnersByShow[e.showId] = [];
|
|
||||||
partnersByShow[e.showId].push({
|
|
||||||
seasonNumber: e.seasonNumber,
|
|
||||||
partner: e.partner ?? undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const byShow: Record<number, number[]> = Object.fromEntries(
|
|
||||||
Object.entries(byShowSet).map(([showId, seasons]) => [
|
|
||||||
Number(showId),
|
|
||||||
Array.from(seasons).sort((a, b) => a - b),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.values(partnersByShow).forEach((arr) =>
|
|
||||||
arr.sort((a, b) => a.seasonNumber - b.seasonNumber)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
raw: entries,
|
|
||||||
byShow,
|
|
||||||
showIds: Object.keys(byShow)
|
|
||||||
.map(Number)
|
|
||||||
.sort((a, b) => a - b),
|
|
||||||
partnersByShow,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchAndCache = useCallback(async (personId: number) => {
|
|
||||||
setLoading((l) => ({ ...l, [personId]: true }));
|
|
||||||
setErrors((e) => ({ ...e, [personId]: null }));
|
|
||||||
try {
|
|
||||||
const data = await getPersonHistory(personId);
|
|
||||||
const appearances = buildAppearances(data);
|
|
||||||
setCache((c) => ({ ...c, [personId]: appearances }));
|
|
||||||
return appearances;
|
|
||||||
} catch (e: any) {
|
|
||||||
setErrors((err) => ({
|
|
||||||
...err,
|
|
||||||
[personId]: e?.message || "Fehler beim Laden",
|
|
||||||
}));
|
|
||||||
return { raw: [], byShow: {}, showIds: [], partnersByShow: {} };
|
|
||||||
} finally {
|
|
||||||
setLoading((l) => ({ ...l, [personId]: false }));
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getPersonAppearances = useCallback(
|
|
||||||
async (personId: number) => {
|
|
||||||
if (cache[personId]) return cache[personId];
|
|
||||||
return await fetchAndCache(personId);
|
|
||||||
},
|
|
||||||
[cache, fetchAndCache]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getShowIds = useCallback(
|
|
||||||
async (personId: number) => {
|
|
||||||
const app = await getPersonAppearances(personId);
|
|
||||||
return app.showIds;
|
|
||||||
},
|
|
||||||
[getPersonAppearances]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getSeasonsForShow = useCallback(
|
|
||||||
async (personId: number, showId: number) => {
|
|
||||||
const app = await getPersonAppearances(personId);
|
|
||||||
return (app.byShow as Record<number, number[]>)[showId] || [];
|
|
||||||
},
|
|
||||||
[getPersonAppearances]
|
|
||||||
);
|
|
||||||
|
|
||||||
const isLoading = (personId: number) => !!loading[personId];
|
|
||||||
const getError = (personId: number) => errors[personId] || null;
|
|
||||||
|
|
||||||
const invalidatePerson = (personId: number) => {
|
|
||||||
setCache((c) => {
|
|
||||||
const copy = { ...c };
|
|
||||||
delete copy[personId];
|
|
||||||
return copy;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PersonContext.Provider
|
|
||||||
value={{
|
|
||||||
getPersonAppearances,
|
|
||||||
getShowIds,
|
|
||||||
getSeasonsForShow,
|
|
||||||
isLoading,
|
|
||||||
getError,
|
|
||||||
invalidatePerson,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</PersonContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usePersonContext = () => {
|
|
||||||
const ctx = useContext(PersonContext);
|
|
||||||
if (!ctx)
|
|
||||||
throw new Error("usePersonContext must be used within PersonProvider");
|
|
||||||
return ctx;
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { getSeason, SeasonParticipant } from "@/apis/seasonApi";
|
import { getSeason, SeasonParticipant } from "@/apis/seasonApi";
|
||||||
import React, {
|
import React, {
|
||||||
createContext,
|
createContext,
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useState,
|
useState,
|
||||||
@@ -13,17 +12,24 @@ type SeasonContextType = {
|
|||||||
seasonNumber: number
|
seasonNumber: number
|
||||||
) => Promise<SeasonParticipant[]>;
|
) => Promise<SeasonParticipant[]>;
|
||||||
fetchSeasonCount: (showId: number) => Promise<number>;
|
fetchSeasonCount: (showId: number) => Promise<number>;
|
||||||
|
fetchSeasonDates: (
|
||||||
|
showId: number,
|
||||||
|
seasonNumber: number
|
||||||
|
) => Promise<{ startDate?: string; endDate?: string | null } | null>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SeasonContext = createContext<SeasonContextType | null>(null);
|
const SeasonContext = createContext<SeasonContextType | null>(null);
|
||||||
|
|
||||||
export const SeasonProvider = ({ children }: { children: ReactNode }) => {
|
export const SeasonProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
const [seasonCache, setSeasonCache] = useState<
|
const [seasonCache, setSeasonCache] = useState<
|
||||||
Record<string, SeasonParticipant[]>
|
Record<string, SeasonParticipant[]>
|
||||||
>({});
|
>({});
|
||||||
const [seasonCountCache, setSeasonCountCache] = useState<
|
const [seasonCountCache, setSeasonCountCache] = useState<
|
||||||
Record<number, number>
|
Record<number, number>
|
||||||
>({});
|
>({});
|
||||||
|
const [datesCache, setDatesCache] = useState<
|
||||||
|
Record<string, { startDate?: string; endDate?: string | null }>
|
||||||
|
>({});
|
||||||
|
|
||||||
const fetchSeasonParticipants = useCallback(
|
const fetchSeasonParticipants = useCallback(
|
||||||
async (showId: number, seasonNumber: number) => {
|
async (showId: number, seasonNumber: number) => {
|
||||||
@@ -61,15 +67,34 @@ export const SeasonProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
[seasonCountCache]
|
[seasonCountCache]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fetchSeasonDates = useCallback(
|
||||||
|
async (showId: number, seasonNumber: number) => {
|
||||||
|
const key = `${showId}-${seasonNumber}`;
|
||||||
|
if (datesCache[key]) return datesCache[key];
|
||||||
|
try {
|
||||||
|
const season = await getSeason(showId, seasonNumber);
|
||||||
|
const dates = season
|
||||||
|
? { startDate: season.startDate, endDate: season.endDate }
|
||||||
|
: null;
|
||||||
|
if (dates) setDatesCache((c) => ({ ...c, [key]: dates }));
|
||||||
|
return dates;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[datesCache]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SeasonContext.Provider
|
<SeasonContext.Provider
|
||||||
value={{ fetchSeasonParticipants, fetchSeasonCount }}
|
value={{ fetchSeasonParticipants, fetchSeasonCount, fetchSeasonDates }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SeasonContext.Provider>
|
</SeasonContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useSeasonContext = () => {
|
export const useSeasonContext = () => {
|
||||||
const ctx = useContext(SeasonContext);
|
const ctx = useContext(SeasonContext);
|
||||||
if (!ctx)
|
if (!ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user