mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-14 13:55:55 -07:00
feat(stats): add library entry deletion and app-wide delete progress (#174)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useAnimeDetail } from '../../hooks/useAnimeDetail';
|
||||
import { getStatsClient } from '../../hooks/useStatsApi';
|
||||
import { confirmAnimeDelete } from '../../lib/delete-confirm';
|
||||
import { epochDayToDate } from '../../lib/formatters';
|
||||
import { AnimeHeader } from './AnimeHeader';
|
||||
import { EpisodeList } from './EpisodeList';
|
||||
@@ -16,6 +17,14 @@ interface AnimeDetailViewProps {
|
||||
onBack: () => void;
|
||||
onNavigateToWord?: (wordId: number) => void;
|
||||
onOpenEpisodeDetail?: (videoId: number) => void;
|
||||
/** Called after the whole library entry is deleted, so the caller can refresh. */
|
||||
onAnimeDeleted?: () => void;
|
||||
/**
|
||||
* Called after the AniList link changes. The library list caches the old
|
||||
* anilistId (and with it the cover URL), so it has to refetch or the grid
|
||||
* keeps showing the previous title's art.
|
||||
*/
|
||||
onAnilistRelinked?: () => void;
|
||||
}
|
||||
|
||||
type Range = 14 | 30 | 90;
|
||||
@@ -139,10 +148,15 @@ export function AnimeDetailView({
|
||||
onBack,
|
||||
onNavigateToWord,
|
||||
onOpenEpisodeDetail,
|
||||
onAnimeDeleted,
|
||||
onAnilistRelinked,
|
||||
}: AnimeDetailViewProps) {
|
||||
const { data, loading, error, reload } = useAnimeDetail(animeId);
|
||||
const [showAnilistSelector, setShowAnilistSelector] = useState(false);
|
||||
const [coverRetryToken, setCoverRetryToken] = useState(0);
|
||||
const [isDeletingAnime, setIsDeletingAnime] = useState(false);
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
const isDeletingAnimeRef = useRef(false);
|
||||
const knownWordsSummary = useAnimeKnownWords(animeId);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -154,6 +168,40 @@ export function AnimeDetailView({
|
||||
if (!data?.detail) return <div className="text-ctp-overlay2 p-4">Anime not found</div>;
|
||||
|
||||
const { detail, episodes, anilistEntries } = data;
|
||||
|
||||
const handleDeleteAnime = async () => {
|
||||
if (isDeletingAnimeRef.current) return;
|
||||
isDeletingAnimeRef.current = true;
|
||||
// Cleared up front so cancelling a retry doesn't leave the previous
|
||||
// attempt's failure on screen.
|
||||
setDeleteError(null);
|
||||
let confirmed = false;
|
||||
try {
|
||||
confirmed = await confirmAnimeDelete(detail.canonicalTitle, detail.episodeCount);
|
||||
} catch (err) {
|
||||
setDeleteError(err instanceof Error ? err.message : 'Failed to confirm delete.');
|
||||
isDeletingAnimeRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (!confirmed) {
|
||||
isDeletingAnimeRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
setDeleteError(null);
|
||||
setIsDeletingAnime(true);
|
||||
try {
|
||||
await getStatsClient().deleteAnime(animeId);
|
||||
onAnimeDeleted?.();
|
||||
onBack();
|
||||
} catch (err) {
|
||||
setDeleteError(err instanceof Error ? err.message : 'Failed to delete this title.');
|
||||
setIsDeletingAnime(false);
|
||||
} finally {
|
||||
isDeletingAnimeRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
@@ -168,7 +216,10 @@ export function AnimeDetailView({
|
||||
anilistEntries={anilistEntries ?? []}
|
||||
coverRetryToken={coverRetryToken}
|
||||
onChangeAnilist={() => setShowAnilistSelector(true)}
|
||||
onDeleteAnime={() => void handleDeleteAnime()}
|
||||
isDeletingAnime={isDeletingAnime}
|
||||
/>
|
||||
{deleteError ? <div className="text-sm text-ctp-red">{deleteError}</div> : null}
|
||||
<AnimeOverviewStats detail={detail} knownWordsSummary={knownWordsSummary} />
|
||||
<EpisodeList
|
||||
episodes={episodes}
|
||||
@@ -185,6 +236,7 @@ export function AnimeDetailView({
|
||||
setShowAnilistSelector(false);
|
||||
setCoverRetryToken((value) => value + 1);
|
||||
reload();
|
||||
onAnilistRelinked?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user