146 lines
4.2 KiB
TypeScript
146 lines
4.2 KiB
TypeScript
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;
|
|
};
|