diff --git a/api/src/main/java/telegram/files/TelegramVerticle.java b/api/src/main/java/telegram/files/TelegramVerticle.java index e858b51..c74bf1b 100644 --- a/api/src/main/java/telegram/files/TelegramVerticle.java +++ b/api/src/main/java/telegram/files/TelegramVerticle.java @@ -175,8 +175,8 @@ public class TelegramVerticle extends AbstractVerticle { } public Future getChatFiles(long chatId, Map 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 -> diff --git a/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java b/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java index c3dec68..13af4c0 100644 --- a/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java +++ b/api/src/main/java/telegram/files/repository/impl/FileRepositoryImpl.java @@ -78,7 +78,7 @@ public class FileRepositoryImpl implements FileRepository { @Override public Future, Long, Long>> getFiles(long chatId, Map 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 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}"; diff --git a/web/src/components/file-filters.tsx b/web/src/components/file-filters.tsx index 135a00a..147c2bd 100644 --- a/web/src/components/file-filters.tsx +++ b/web/src/components/file-filters.tsx @@ -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 ( +
+ {downloadStatus && ( +
+ Download + +
+ )} + {transferStatus && ( +
+ Transfer + +
+ )} +
+ ); + }, [filters.downloadStatus, filters.transferStatus]); + useEffect(() => { handleSearchChange(search); }, [search, handleSearchChange]); return (
-
-
+
+
setSearch(e.target.value)} - className="relative col-span-2 md:w-[300px]" + className="relative md:w-[300px]" /> {search && (
diff --git a/web/src/components/file-status.tsx b/web/src/components/file-status.tsx index 7e04a3a..74336bf 100644 --- a/web/src/components/file-status.tsx +++ b/web/src/components/file-status.tsx @@ -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: { diff --git a/web/src/hooks/use-files.ts b/web/src/hooks/use-files.ts index 6b5fea8..b2e3e1b 100644 --- a/web/src/hooks/use-files.ts +++ b/web/src/hooks/use-files.ts @@ -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; } diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 78ab4ef..ca063ef 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -80,7 +80,8 @@ export type TDFile = { export type FileFilter = { search: string; type: FileType; - status: DownloadStatus | "all"; + downloadStatus?: DownloadStatus; + transferStatus?: TransferStatus; }; export type TelegramApiResult = {