✨ feat: Support transfer status filter.
This commit is contained in:
parent
1575a84a9b
commit
8f48c7939f
6 changed files with 191 additions and 82 deletions
|
|
@ -175,8 +175,8 @@ public class TelegramVerticle extends AbstractVerticle {
|
|||
}
|
||||
|
||||
public Future<JsonObject> getChatFiles(long chatId, Map<String, String> filter) {
|
||||
String status = filter.get("status");
|
||||
if (Arrays.asList("downloading", "paused", "completed", "error").contains(status)) {
|
||||
String downloadStatus = filter.get("downloadStatus");
|
||||
if (Arrays.asList("downloading", "paused", "completed", "error").contains(downloadStatus)) {
|
||||
return DataVerticle.fileRepository.getFiles(chatId, filter)
|
||||
.compose(r -> {
|
||||
long[] messageIds = r.v1.stream().mapToLong(FileRecord::messageId).toArray();
|
||||
|
|
@ -215,7 +215,7 @@ public class TelegramVerticle extends AbstractVerticle {
|
|||
searchChatMessages.limit = Convert.toInt(filter.get("limit"), 20);
|
||||
searchChatMessages.filter = TdApiHelp.getSearchMessagesFilter(filter.get("type"));
|
||||
|
||||
return (Objects.equals(filter.get("status"), FileRecord.DownloadStatus.idle.name()) ?
|
||||
return (Objects.equals(filter.get("downloadStatus"), FileRecord.DownloadStatus.idle.name()) ?
|
||||
this.getIdleChatFiles(searchChatMessages, 0) :
|
||||
client.execute(searchChatMessages))
|
||||
.compose(foundChatMessages ->
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public class FileRepositoryImpl implements FileRepository {
|
|||
|
||||
@Override
|
||||
public Future<Tuple3<List<FileRecord>, Long, Long>> getFiles(long chatId, Map<String, String> filter) {
|
||||
String status = filter.get("status");
|
||||
String downloadStatus = filter.get("downloadStatus");
|
||||
String transferStatus = filter.get("transferStatus");
|
||||
String search = filter.get("search");
|
||||
Long fromMessageId = Convert.toLong(filter.get("fromMessageId"), 0L);
|
||||
|
|
@ -88,9 +88,9 @@ public class FileRepositoryImpl implements FileRepository {
|
|||
String whereClause = "chat_id = #{chatId}";
|
||||
Map<String, Object> params = MapUtil.of("chatId", chatId);
|
||||
params.put("limit", limit);
|
||||
if (StrUtil.isNotBlank(status)) {
|
||||
whereClause += " AND download_status = #{status}";
|
||||
params.put("status", status);
|
||||
if (StrUtil.isNotBlank(downloadStatus)) {
|
||||
whereClause += " AND download_status = #{downloadStatus}";
|
||||
params.put("downloadStatus", downloadStatus);
|
||||
}
|
||||
if (StrUtil.isNotBlank(transferStatus)) {
|
||||
whereClause += " AND transfer_status = #{transferStatus}";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import { type DownloadStatus, type FileFilter } from "@/lib/types";
|
||||
import {
|
||||
type DownloadStatus,
|
||||
type FileFilter,
|
||||
type TransferStatus,
|
||||
} from "@/lib/types";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectGroup,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
|
@ -14,9 +19,13 @@ import { type RowHeight } from "@/components/table-row-height-switch";
|
|||
import FileTypeFilter from "@/components/file-type-filter";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { useEffect, useState } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { DOWNLOAD_STATUS, TRANSFER_STATUS } from "@/components/file-status";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface FileFiltersProps {
|
||||
telegramId: string;
|
||||
|
|
@ -37,6 +46,17 @@ const TableRowHeightSwitch = dynamic(
|
|||
{ ssr: false },
|
||||
);
|
||||
|
||||
const statusOptions = {
|
||||
downloadStatus: {
|
||||
text: "Download Status",
|
||||
status: DOWNLOAD_STATUS,
|
||||
},
|
||||
transferStatus: {
|
||||
text: "Transfer Status",
|
||||
status: TRANSFER_STATUS,
|
||||
},
|
||||
};
|
||||
|
||||
export function FileFilters({
|
||||
telegramId,
|
||||
chatId,
|
||||
|
|
@ -53,19 +73,69 @@ export function FileFilters({
|
|||
onFiltersChange({ ...filters, search });
|
||||
}, 500);
|
||||
|
||||
const handleStatusSelect = (value: string) => {
|
||||
const [group, item] = value.split("||");
|
||||
|
||||
if (group === "downloadStatus") {
|
||||
onFiltersChange({
|
||||
...filters,
|
||||
downloadStatus:
|
||||
item === filters.downloadStatus
|
||||
? undefined
|
||||
: (item as DownloadStatus),
|
||||
});
|
||||
} else if (group === "transferStatus") {
|
||||
onFiltersChange({
|
||||
...filters,
|
||||
transferStatus:
|
||||
item === filters.transferStatus
|
||||
? undefined
|
||||
: (item as TransferStatus),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const statusDisplayValue = useMemo(() => {
|
||||
if (!filters.downloadStatus && !filters.transferStatus) {
|
||||
console.log("no status filter");
|
||||
return "Filter Status";
|
||||
}
|
||||
const downloadStatus =
|
||||
filters.downloadStatus && DOWNLOAD_STATUS[filters.downloadStatus];
|
||||
const transferStatus =
|
||||
filters.transferStatus && TRANSFER_STATUS[filters.transferStatus];
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
{downloadStatus && (
|
||||
<div className="flex items-center space-x-1 rounded bg-gray-100 p-1 dark:bg-gray-800">
|
||||
<span className="text-xs">Download</span>
|
||||
<downloadStatus.icon className="h-3 w-3" />
|
||||
</div>
|
||||
)}
|
||||
{transferStatus && (
|
||||
<div className="flex items-center space-x-1 rounded bg-gray-100 p-1 dark:bg-gray-800">
|
||||
<span className="text-xs">Transfer</span>
|
||||
<transferStatus.icon className="h-3 w-3" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [filters.downloadStatus, filters.transferStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
handleSearchChange(search);
|
||||
}, [search, handleSearchChange]);
|
||||
|
||||
return (
|
||||
<div className="mb-6 flex flex-col justify-between md:flex-row">
|
||||
<div className="grid grid-cols-2 gap-4 md:flex md:flex-row">
|
||||
<div className="relative">
|
||||
<div className="grid grid-cols-3 gap-4 md:flex md:flex-row">
|
||||
<div className="relative col-span-3">
|
||||
<Input
|
||||
placeholder="Search files..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="relative col-span-2 md:w-[300px]"
|
||||
className="relative md:w-[300px]"
|
||||
/>
|
||||
{search && (
|
||||
<Button
|
||||
|
|
@ -87,24 +157,41 @@ export function FileFilters({
|
|||
/>
|
||||
|
||||
<Select
|
||||
value={filters.status}
|
||||
onValueChange={(value) =>
|
||||
onFiltersChange({
|
||||
...filters,
|
||||
status: value as DownloadStatus | "all",
|
||||
})
|
||||
}
|
||||
value={`${filters.downloadStatus ?? ""}||${filters.transferStatus ?? ""}`}
|
||||
onValueChange={(value) => handleStatusSelect(value)}
|
||||
>
|
||||
<SelectTrigger className="w-full md:w-[150px]">
|
||||
<SelectValue placeholder="Download status" />
|
||||
<SelectTrigger className="col-span-2 w-full md:w-[210px]">
|
||||
<SelectValue placeholder="Filter Status">
|
||||
{statusDisplayValue}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All statuses</SelectItem>
|
||||
<SelectItem value="idle">Not downloaded</SelectItem>
|
||||
<SelectItem value="downloading">Downloading</SelectItem>
|
||||
<SelectItem value="paused">Paused</SelectItem>
|
||||
<SelectItem value="completed">Completed</SelectItem>
|
||||
<SelectItem value="error">Error</SelectItem>
|
||||
{Object.entries(statusOptions).map(([groupKey, group]) => (
|
||||
<SelectGroup key={groupKey}>
|
||||
<SelectLabel>{group.text}</SelectLabel>
|
||||
{Object.entries(group.status).map(([statusKey, item]) => (
|
||||
<SelectPrimitive.Item
|
||||
key={`${groupKey}||${statusKey}`}
|
||||
value={`${groupKey}||${statusKey}`}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
)}
|
||||
>
|
||||
{filters[groupKey as keyof FileFilter] === statusKey && (
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<Check className="h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
<SelectPrimitive.ItemText>
|
||||
<div className="flex items-center">
|
||||
{item.icon && <item.icon className="mr-2 h-4 w-4" />}
|
||||
{item.text}
|
||||
</div>
|
||||
</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))}
|
||||
</SelectGroup>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,50 +4,60 @@ import React from "react";
|
|||
import { cn } from "@/lib/utils";
|
||||
import { TooltipWrapper } from "@/components/ui/tooltip";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {CheckCircle2, Clock, Download, FolderSync, Pause, XCircle} from "lucide-react";
|
||||
|
||||
export const DOWNLOAD_STATUS = {
|
||||
idle: {
|
||||
icon: Clock,
|
||||
className: "bg-gray-100 text-gray-600",
|
||||
text: "Idle",
|
||||
},
|
||||
downloading: {
|
||||
icon: Download,
|
||||
className: "bg-blue-100 text-blue-600",
|
||||
text: "Downloading",
|
||||
},
|
||||
paused: {
|
||||
icon: Pause,
|
||||
className: "bg-yellow-100 text-yellow-600",
|
||||
text: "Paused",
|
||||
},
|
||||
completed: {
|
||||
icon: CheckCircle2,
|
||||
className: "bg-green-100 text-green-600",
|
||||
text: "Completed",
|
||||
},
|
||||
error: {
|
||||
icon: XCircle,
|
||||
className: "bg-red-100 text-red-600",
|
||||
text: "Error",
|
||||
},
|
||||
};
|
||||
|
||||
export const TRANSFER_STATUS = {
|
||||
idle: {
|
||||
icon: Clock,
|
||||
className: "bg-gray-100 text-gray-600",
|
||||
text: "Idle",
|
||||
},
|
||||
transferring: {
|
||||
icon: FolderSync,
|
||||
className: "bg-blue-100 text-blue-600",
|
||||
text: "Transferring",
|
||||
},
|
||||
completed: {
|
||||
icon: CheckCircle2,
|
||||
className: "bg-green-100 text-green-600",
|
||||
text: "Transferred",
|
||||
},
|
||||
error: {
|
||||
icon: XCircle,
|
||||
className: "bg-red-100 text-red-600",
|
||||
text: "Transfer Error",
|
||||
},
|
||||
};
|
||||
|
||||
export default function FileStatus({ file }: { file: TelegramFile }) {
|
||||
const DOWNLOAD_STATUS = {
|
||||
idle: {
|
||||
className: "bg-gray-100 text-gray-600",
|
||||
text: "Idle",
|
||||
},
|
||||
downloading: {
|
||||
className: "bg-blue-100 text-blue-600",
|
||||
text: "Downloading",
|
||||
},
|
||||
paused: {
|
||||
className: "bg-yellow-100 text-yellow-600",
|
||||
text: "Paused",
|
||||
},
|
||||
completed: {
|
||||
className: "bg-green-100 text-green-600",
|
||||
text: "Completed",
|
||||
},
|
||||
error: {
|
||||
className: "bg-red-100 text-red-600",
|
||||
text: "Error",
|
||||
},
|
||||
};
|
||||
|
||||
const TRANSFER_STATUS = {
|
||||
idle: {
|
||||
className: "bg-gray-100 text-gray-600",
|
||||
text: "Idle",
|
||||
},
|
||||
transferring: {
|
||||
className: "bg-blue-100 text-blue-600",
|
||||
text: "Transferring",
|
||||
},
|
||||
completed: {
|
||||
className: "bg-green-100 text-green-600",
|
||||
text: "Transferred",
|
||||
},
|
||||
error: {
|
||||
className: "bg-red-100 text-red-600",
|
||||
text: "Transfer Error",
|
||||
},
|
||||
};
|
||||
|
||||
const badgeVariants = {
|
||||
initial: { opacity: 0, scale: 0.9 },
|
||||
animate: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { useEffect, useMemo, useState } from "react";
|
|||
import {
|
||||
type DownloadStatus,
|
||||
type FileFilter,
|
||||
type TelegramFile, type TransferStatus,
|
||||
type TelegramFile,
|
||||
type TransferStatus,
|
||||
} from "@/lib/types";
|
||||
import useSWRInfinite from "swr/infinite";
|
||||
import { useWebsocket } from "@/hooks/use-websocket";
|
||||
|
|
@ -13,7 +14,8 @@ import { useDebounce } from "use-debounce";
|
|||
const DEFAULT_FILTERS: FileFilter = {
|
||||
search: "",
|
||||
type: "media",
|
||||
status: "all",
|
||||
downloadStatus: undefined,
|
||||
transferStatus: undefined,
|
||||
};
|
||||
|
||||
type FileResponse = {
|
||||
|
|
@ -43,9 +45,12 @@ export function useFiles(accountId: string, chatId: string) {
|
|||
);
|
||||
const getKey = (page: number, previousPageData: FileResponse) => {
|
||||
const params = new URLSearchParams({
|
||||
...(filters.search && { search: window.encodeURIComponent(filters.search) }),
|
||||
...(filters.search && {
|
||||
search: window.encodeURIComponent(filters.search),
|
||||
}),
|
||||
...(filters.type && { type: filters.type }),
|
||||
...(filters.status && { status: filters.status })
|
||||
...(filters.downloadStatus && { downloadStatus: filters.downloadStatus }),
|
||||
...(filters.transferStatus && { transferStatus: filters.transferStatus }),
|
||||
});
|
||||
|
||||
if (page === 0) {
|
||||
|
|
@ -133,14 +138,19 @@ export function useFiles(accountId: string, chatId: string) {
|
|||
...file,
|
||||
id: latestFilesStatus[file.uniqueId]?.fileId ?? file.id,
|
||||
downloadStatus:
|
||||
latestFilesStatus[file.uniqueId]?.downloadStatus ?? file.downloadStatus,
|
||||
localPath: latestFilesStatus[file.uniqueId]?.localPath ?? file.localPath,
|
||||
latestFilesStatus[file.uniqueId]?.downloadStatus ??
|
||||
file.downloadStatus,
|
||||
localPath:
|
||||
latestFilesStatus[file.uniqueId]?.localPath ?? file.localPath,
|
||||
completionDate:
|
||||
latestFilesStatus[file.uniqueId]?.completionDate ?? file.completionDate,
|
||||
latestFilesStatus[file.uniqueId]?.completionDate ??
|
||||
file.completionDate,
|
||||
downloadedSize:
|
||||
latestFilesStatus[file.uniqueId]?.downloadedSize ?? file.downloadedSize,
|
||||
latestFilesStatus[file.uniqueId]?.downloadedSize ??
|
||||
file.downloadedSize,
|
||||
transferStatus:
|
||||
latestFilesStatus[file.uniqueId]?.transferStatus ?? file.transferStatus,
|
||||
latestFilesStatus[file.uniqueId]?.transferStatus ??
|
||||
file.transferStatus,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -169,7 +179,8 @@ export function useFiles(accountId: string, chatId: string) {
|
|||
if (
|
||||
newFilters.search === filters.search &&
|
||||
newFilters.type === filters.type &&
|
||||
newFilters.status === filters.status
|
||||
newFilters.downloadStatus === filters.downloadStatus &&
|
||||
newFilters.transferStatus === filters.transferStatus
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ export type TDFile = {
|
|||
export type FileFilter = {
|
||||
search: string;
|
||||
type: FileType;
|
||||
status: DownloadStatus | "all";
|
||||
downloadStatus?: DownloadStatus;
|
||||
transferStatus?: TransferStatus;
|
||||
};
|
||||
|
||||
export type TelegramApiResult = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue