feat(timer): sıralama özelliği ekle
This commit is contained in:
@@ -16,6 +16,13 @@ import {
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "../components/ui/AlertDialog";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../components/ui/Select";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faClockRotateLeft,
|
||||
@@ -33,6 +40,14 @@ const unitOptions = [
|
||||
{ label: "Hafta", value: "weeks", seconds: 604800 },
|
||||
] as const;
|
||||
|
||||
const sortOptions = [
|
||||
{ label: "İsim", value: "name" },
|
||||
{ label: "Boyut", value: "size" },
|
||||
{ label: "Geri Sayım", value: "countdown" },
|
||||
{ label: "Tracker", value: "tracker" },
|
||||
{ label: "Eklenme Tarihi", value: "addedOn" },
|
||||
] as const;
|
||||
|
||||
const formatBytes = (value: number) => {
|
||||
if (!Number.isFinite(value)) return "0 B";
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
@@ -96,6 +111,9 @@ export const TimerPage = () => {
|
||||
const [seedUnit, setSeedUnit] = useState<(typeof unitOptions)[number]["value"]>(
|
||||
"weeks"
|
||||
);
|
||||
const [sortKey, setSortKey] =
|
||||
useState<(typeof sortOptions)[number]["value"]>("countdown");
|
||||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
||||
const [deleteFiles, setDeleteFiles] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const pushAlert = useUiStore((s) => s.pushAlert);
|
||||
@@ -142,11 +160,7 @@ export const TimerPage = () => {
|
||||
const rule = matchingRules.reduce((best, current) =>
|
||||
current.seedLimitSeconds < best.seedLimitSeconds ? current : best
|
||||
);
|
||||
const ruleCreatedAtMs = Date.parse(rule.createdAt);
|
||||
let baseMs = addedOnMs || nowTick;
|
||||
if (Number.isFinite(ruleCreatedAtMs) && ruleCreatedAtMs > baseMs) {
|
||||
baseMs = ruleCreatedAtMs;
|
||||
}
|
||||
const baseMs = addedOnMs || nowTick;
|
||||
const elapsedSeconds = Math.max(0, (nowTick - baseMs) / 1000);
|
||||
const remainingSeconds = rule.seedLimitSeconds - elapsedSeconds;
|
||||
return {
|
||||
@@ -162,6 +176,47 @@ export const TimerPage = () => {
|
||||
}>;
|
||||
}, [timerRules, torrents, nowTick]);
|
||||
|
||||
const sortedMatchingTorrents = useMemo(() => {
|
||||
const direction = sortDirection === "asc" ? 1 : -1;
|
||||
const withFallback = (value: number | string | undefined, fallback: number | string) =>
|
||||
value === undefined || value === null || value === "" ? fallback : value;
|
||||
|
||||
return [...matchingTorrents].sort((a, b) => {
|
||||
switch (sortKey) {
|
||||
case "name":
|
||||
return (
|
||||
String(withFallback(a.torrent.name, ""))
|
||||
.localeCompare(String(withFallback(b.torrent.name, "")), "tr") *
|
||||
direction
|
||||
);
|
||||
case "size":
|
||||
return (
|
||||
(Number(withFallback(a.torrent.size, 0)) -
|
||||
Number(withFallback(b.torrent.size, 0))) *
|
||||
direction
|
||||
);
|
||||
case "tracker":
|
||||
return (
|
||||
trackerLabel(a.torrent.tracker)
|
||||
.localeCompare(trackerLabel(b.torrent.tracker), "tr") * direction
|
||||
);
|
||||
case "addedOn":
|
||||
return (
|
||||
(Number(withFallback(a.torrent.added_on, 0)) -
|
||||
Number(withFallback(b.torrent.added_on, 0))) *
|
||||
direction
|
||||
);
|
||||
case "countdown":
|
||||
default:
|
||||
return (
|
||||
(Number(withFallback(a.remainingSeconds, 0)) -
|
||||
Number(withFallback(b.remainingSeconds, 0))) *
|
||||
direction
|
||||
);
|
||||
}
|
||||
});
|
||||
}, [matchingTorrents, sortDirection, sortKey]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
const load = async () => {
|
||||
@@ -282,19 +337,54 @@ export const TimerPage = () => {
|
||||
<div className="min-w-0 space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faHourglassHalf} className="text-slate-400" />
|
||||
Zamanlayıcı Torrentleri
|
||||
</CardTitle>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faHourglassHalf} className="text-slate-400" />
|
||||
Zamanlayıcı Torrentleri
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2 text-xs font-semibold text-slate-500">
|
||||
<span>Sıralama</span>
|
||||
<Select
|
||||
value={sortKey}
|
||||
onValueChange={(value) => {
|
||||
if (value !== sortKey) {
|
||||
setSortKey(value as typeof sortKey);
|
||||
setSortDirection("asc");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9 min-w-[180px] text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{sortOptions.map((option) => (
|
||||
<SelectItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
onClick={() => {
|
||||
if (option.value === sortKey) {
|
||||
setSortDirection((current) =>
|
||||
current === "asc" ? "desc" : "asc"
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{matchingTorrents.length === 0 ? (
|
||||
{sortedMatchingTorrents.length === 0 ? (
|
||||
<div className="text-sm text-slate-500">
|
||||
Bu kurallara bağlı aktif torrent bulunamadı.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{matchingTorrents.map(({ torrent, rule, remainingSeconds }) => (
|
||||
{sortedMatchingTorrents.map(({ torrent, rule, remainingSeconds }) => (
|
||||
<div
|
||||
key={torrent.hash}
|
||||
className="rounded-lg border border-slate-200 bg-white px-3 py-2"
|
||||
|
||||
Reference in New Issue
Block a user