frontend/components/manga-chapter.tsx

143 lines
7.5 KiB
TypeScript

import {FC, useCallback, useState} from "react";
import {useDownloadChapterArchive, useFetchChapter, useGetMangaChapters} from "@/api/mangamochi";
import {Check, Database, Download, Eye, Loader2} from "lucide-react";
import {Button} from "@/components/ui/button";
import {useQueryClient} from "@tanstack/react-query";
import {DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger} from "@/components/ui/dropdown-menu";
import {useRouter} from "next/navigation";
interface MangaChapterProps {
mangaId: number;
mangaProviderId: number;
}
export const MangaChapter: FC<MangaChapterProps> = ({mangaId, mangaProviderId}) => {
const router = useRouter()
const { isPending, data, queryKey } = useGetMangaChapters(mangaProviderId);
const queryClient = useQueryClient();
const { mutate: mutateDownloadChapterArchive, isPending: isPendingDownloadChapter } = useDownloadChapterArchive({
mutation: {
onSuccess: (data, {chapterId}) => {
const url = window.URL.createObjectURL(data);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', chapterId + '.cbz');
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
}
}
})
const { mutate, isPending: isPendingFetchChapter } = useFetchChapter({
mutation: {
onSuccess: () => queryClient.invalidateQueries({queryKey}),
onSettled: () => setFetchingId(null)
}
});
const [fetchingId, setFetchingId] = useState<number | null>(null);
const fetchChapter = useCallback((mangaChapterId: number) => {
setFetchingId(mangaChapterId);
mutate({chapterId: mangaChapterId});
}, [mutate]);
const handleReadChapter = (chapterNumber: number) => {
router.push(`/manga/${mangaId}/chapter/${chapterNumber}`)
}
return (<div className="border-t border-border px-4 pb-4">
{
isPending ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-primary" />
<span className="ml-2 text-sm text-muted-foreground">Loading chapters...</span>
</div>
) :
data ? (
<div className="mt-4 space-y-2">
{data.map((chapter) => {
return (
<div
key={chapter.id}
className="flex items-center justify-between rounded-lg border border-border bg-background p-3"
>
<div className="flex items-center gap-3">
<div
className={`flex h-8 w-8 items-center justify-center rounded-full ${
chapter.isRead ? "bg-primary/20" : "bg-muted"
}`}
>
{chapter.isRead ? (
<Check className="h-4 w-4 text-primary" />
) : (
<span className="text-xs font-medium text-muted-foreground">
{/*{chapter}*/}
</span>
)}
</div>
<div>
<p className="text-sm font-medium text-foreground">{chapter.title}</p>
{chapter.downloaded && (
<p className="text-xs text-muted-foreground">In database</p>
)}
</div>
</div>
{chapter.downloaded ? (
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => handleReadChapter(chapter.id)}
className="gap-2"
>
<Eye className="h-4 w-4" />
Read
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" className="gap-2">
<Download className="h-4 w-4" />
Download
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
disabled={isPendingDownloadChapter}
onClick={() => mutateDownloadChapterArchive({chapterId: chapter.id, params: {archiveFileType: 'CBZ'} })}
>
Download as CBZ
</DropdownMenuItem>
<DropdownMenuItem
// onClick={() => handleDownloadToDevice(provider.name, chapter.number, "cbr")}
disabled
>
Download as CBR
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
) : (
<Button
size="sm"
variant="outline"
onClick={() => fetchChapter(chapter.id)}
disabled={isPendingFetchChapter}
className="gap-2 cursor-pointer"
>
<Database className="h-4 w-4" />
{(isPendingFetchChapter && fetchingId == chapter.id) ? "Fetching..." : "Fetch from Provider"}
</Button>
)}
</div>
)
})}
</div>
) : null}
</div>);
};