update torrents list view

This commit is contained in:
2026-02-19 18:10:03 +03:00
parent 7aa0e8fd30
commit b92a1216f0
2 changed files with 118 additions and 75 deletions

View File

@@ -5,20 +5,15 @@ import {
ArrowsClockwiseIcon,
CaretDownIcon,
CaretUpIcon,
CheckCircleIcon,
DownloadSimpleIcon,
TrashIcon,
} from "@phosphor-icons/react";
import { useDownloadTorrentMutation } from "../api/useDownloadTorrentMutation";
import { useDeleteItemMutation } from "../api/useDeleteItemMutation";
import { useQueryClient } from "@tanstack/react-query";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { humanFileSize } from "../utils/humanFileSize";
import { useDeleteTorrentMutation } from "../api/useDeleteTorrentMutation";
import { useRefreshItemMutation } from "../api/useRefreshItemMutation";
import { Loader } from "./Loader";
import { categories } from "../lib/categories";
import { Torrent } from "./Torrent";
dayjs.extend(relativeTime);
@@ -34,9 +29,7 @@ export const Item = ({ item }: ItemProps) => {
const { data: torrents } = useItemTorrentsQuery(item.id, open);
const deleteMutation = useDeleteItemMutation();
const downloadMutation = useDownloadTorrentMutation();
const refreshMutation = useRefreshItemMutation();
const deleteTorrentMutation = useDeleteTorrentMutation();
const Icon = open ? CaretUpIcon : CaretDownIcon;
@@ -61,10 +54,6 @@ export const Item = ({ item }: ItemProps) => {
}
}, [item]);
const handleDownloadTorrent = (torrentId: number) => {
downloadMutation.mutate({ torrentId });
};
const handleDelete = () => {
if (!confirm("Do you want to delete this item?")) return;
@@ -95,22 +84,6 @@ export const Item = ({ item }: ItemProps) => {
);
};
const handleDeleteTorrent = (torrentId: number) => {
deleteTorrentMutation.mutate(
{
id: torrentId,
},
{
onSuccess() {
queryClient.invalidateQueries({ queryKey: ["items"] });
queryClient.invalidateQueries({
queryKey: ["items", item.id, "torrents"],
});
},
},
);
};
return (
<div className="border-t border-b border-neutral-900">
<div
@@ -163,49 +136,7 @@ export const Item = ({ item }: ItemProps) => {
</div>
{filteredTorrents && filteredTorrents.length > 0 ? (
filteredTorrents.map((torrent) => (
<div
key={torrent.id}
className="flex justify-between items-center hover:bg-neutral-200 group"
>
<div className="flex items-center gap-2">
<span>
<a
href={torrent.guid}
target="_blank"
rel="noopener noreferrer"
>
{torrent.title}
</a>{" "}
[{formatCategory(torrent.category)}] [{torrent.indexer}]
</span>
{torrent.downloaded && (
<span title="Torrent files downloaded">
<CheckCircleIcon size={20} color="green" />
</span>
)}
<button
className="hidden group-hover:inline text-[#b00420] cursor-pointer"
onClick={() => handleDeleteTorrent(torrent.id)}
>
<TrashIcon size={16} />
</button>
</div>
<div className="flex items-center gap-1">
<span>Seeds: {torrent.seeders}</span>
<span>Peers: {torrent.peers}</span>
<span>
PubDate: {dayjs(torrent.pubdate).format("DD.MM.YYYY")} at{" "}
{dayjs(torrent.pubdate).format("HH:mm")}
</span>
<button
className="cursor-pointer"
onClick={() => handleDownloadTorrent(torrent.id)}
>
<DownloadSimpleIcon size={24} />
</button>
<span>{humanFileSize(torrent.size)}</span>
</div>
</div>
<Torrent key={torrent.id} itemId={item.id} torrent={torrent} />
))
) : (
<span>No torrents yet</span>
@@ -215,7 +146,3 @@ export const Item = ({ item }: ItemProps) => {
</div>
);
};
const formatCategory = (category: number): string => {
return categories[category as keyof typeof categories] || "";
};

View File

@@ -0,0 +1,116 @@
import { useState } from "react";
import type { ItemTorrent } from "../api/useItemTorrentsQuery";
import { categories } from "../lib/categories";
import {
ArrowSquareOutIcon,
CaretDownIcon,
CaretUpIcon,
CheckCircleIcon,
DownloadSimpleIcon,
TrashIcon,
} from "@phosphor-icons/react";
import dayjs from "dayjs";
import { humanFileSize } from "../utils/humanFileSize";
import { useDeleteTorrentMutation } from "../api/useDeleteTorrentMutation";
import { useDownloadTorrentMutation } from "../api/useDownloadTorrentMutation";
import { useQueryClient } from "@tanstack/react-query";
export type TorrentProps = {
itemId: number;
torrent: ItemTorrent;
};
export const Torrent = ({ itemId, torrent }: TorrentProps) => {
const queryClient = useQueryClient();
const downloadMutation = useDownloadTorrentMutation();
const deleteMutation = useDeleteTorrentMutation();
const [open, setOpen] = useState(false);
const ChevronIcon = open ? CaretUpIcon : CaretDownIcon;
const handleDownload = () => {
downloadMutation.mutate({ torrentId: torrent.id });
};
const handleDelete = () => {
deleteMutation.mutate(
{
id: torrent.id,
},
{
onSuccess() {
queryClient.invalidateQueries({ queryKey: ["items"] });
queryClient.invalidateQueries({
queryKey: ["items", itemId, "torrents"],
});
},
},
);
};
return (
<div className="flex flex-col gap-1">
<div
className="flex items-center gap-2 hover:bg-neutral-200 cursor-pointer"
role="button"
tabIndex={0}
onClick={() => setOpen((prev) => !prev)}
>
<p>{torrent.title}</p>
{torrent.downloaded && (
<span title="Torrent files downloaded">
<CheckCircleIcon size={20} color="green" />
</span>
)}
<div className="ml-auto flex items-center gap-1">
<span>[{formatCategory(torrent.category)}]</span>
<span>{humanFileSize(torrent.size)}</span>
<span>
<ChevronIcon size={20} />
</span>
</div>
</div>
{open && (
<div className="p-2 bg-neutral-100 rounded-md text-[15px]">
<p>Indexer: {torrent.indexer}</p>
<p>Seeders: {torrent.seeders}</p>
<p>Peers: {torrent.peers}</p>
<p>
Published: {dayjs(torrent.pubdate).format("DD.MM.YYYY")} at{" "}
{dayjs(torrent.pubdate).format("HH:mm")}
</p>
<div className="flex items-center gap-2 mt-2">
<a
href={torrent.guid}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1"
>
<ArrowSquareOutIcon size={18} /> Open
</a>
<button
type="button"
className="cursor-pointer flex items-center gap-1"
onClick={handleDownload}
>
<DownloadSimpleIcon size={18} /> Download
</button>
<button
type="button"
className="text-[#b00420] cursor-pointer flex items-center gap-1"
onClick={handleDelete}
>
<TrashIcon size={18} /> Delete torrent
</button>
</div>
</div>
)}
</div>
);
};
const formatCategory = (category: number): string => {
return categories[category as keyof typeof categories] || "";
};