From 455eef9fa3d300c165c91e556392e4c6f01dc751 Mon Sep 17 00:00:00 2001 From: Richard Szegh Date: Mon, 24 Nov 2025 04:55:53 +0100 Subject: [PATCH] feat(web): add ability to delete unused attachments (#5272) --- web/src/locales/en.json | 2 ++ web/src/pages/Attachments.tsx | 61 ++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 9ab89ee62..22c7a4335 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -231,6 +231,8 @@ "delete-selected-resources": "Delete Selected Resources", "delete-all-unused": "Delete all unused", "delete-all-unused-confirm": "Are you sure you want to delete all unused resources? THIS ACTION IS IRREVERSIBLE", + "delete-all-unused-success": "Resources deleted successfully", + "delete-all-unused-error": "Failed to delete unused resources", "fetching-data": "Fetching data…", "file-drag-drop-prompt": "Drag and drop your file here to upload file", "linked-amount": "Linked amount", diff --git a/web/src/pages/Attachments.tsx b/web/src/pages/Attachments.tsx index 6384f5c8c..2fd99d2b8 100644 --- a/web/src/pages/Attachments.tsx +++ b/web/src/pages/Attachments.tsx @@ -1,19 +1,22 @@ import dayjs from "dayjs"; import { includes } from "lodash-es"; -import { PaperclipIcon, SearchIcon } from "lucide-react"; +import { PaperclipIcon, SearchIcon, Trash } from "lucide-react"; import { observer } from "mobx-react-lite"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import AttachmentIcon from "@/components/AttachmentIcon"; +import ConfirmDialog from "@/components/ConfirmDialog"; import Empty from "@/components/Empty"; import MobileHeader from "@/components/MobileHeader"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; import { attachmentServiceClient } from "@/grpcweb"; +import useDialog from "@/hooks/useDialog"; import useLoading from "@/hooks/useLoading"; import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import i18n from "@/i18n"; +import { attachmentStore } from "@/store"; import { Attachment } from "@/types/proto/api/v1/attachment_service"; import { useTranslate } from "@/utils/i18n"; @@ -39,6 +42,7 @@ const Attachments = observer(() => { const t = useTranslate(); const { md } = useResponsiveWidth(); const loadingState = useLoading(); + const deleteUnusedAttachmentsDialog = useDialog(); const [state, setState] = useState({ searchQuery: "", }); @@ -88,6 +92,37 @@ const Attachments = observer(() => { } }; + const handleRefetch = async () => { + try { + loadingState.setLoading(); + const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({ + pageSize: 50, + }); + setAttachments(fetchedAttachments); + setNextPageToken(nextPageToken ?? ""); + loadingState.setFinish(); + } catch (error) { + console.error(error); + loadingState.setError(); + } + }; + + const handleDeleteUnusedAttachments = async () => { + try { + await Promise.all( + unusedAttachments.map((attachment) => { + return attachmentStore.deleteAttachment(attachment.name); + }), + ); + toast.success(t("resource.delete-all-unused-success")); + } catch (error) { + console.error(error); + toast.error(t("resource.delete-all-unused-error")); + } finally { + void handleRefetch(); + } + }; + return (
{!md && } @@ -158,9 +193,17 @@ const Attachments = observer(() => {
-
- {t("resource.unused-resources")} - ({unusedAttachments.length}) +
+
+ {t("resource.unused-resources")} + ({unusedAttachments.length}) +
+
+ +
{unusedAttachments.map((attachment) => { return ( @@ -193,6 +236,16 @@ const Attachments = observer(() => {
+ +
); });