From 0669e102d3ca9c1aec145e2ca4d4ce50b9492509 Mon Sep 17 00:00:00 2001 From: sawatkins Date: Fri, 5 Dec 2025 17:17:22 -0800 Subject: [PATCH] implement server side attachment filtering --- server/router/api/v1/attachment_service.go | 5 ++++ web/src/pages/Attachments.tsx | 33 +++++++++------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/server/router/api/v1/attachment_service.go b/server/router/api/v1/attachment_service.go index e3768f8e9..d56888637 100644 --- a/server/router/api/v1/attachment_service.go +++ b/server/router/api/v1/attachment_service.go @@ -159,6 +159,11 @@ func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAt Offset: &offset, } + if request.Filter != "" { + filter := strings.TrimSpace(request.Filter) + findAttachment.FilenameSearch = &filter + } + attachments, err := s.Store.ListAttachments(ctx, findAttachment) if err != nil { return nil, status.Errorf(codes.Internal, "failed to list attachments: %v", err) diff --git a/web/src/pages/Attachments.tsx b/web/src/pages/Attachments.tsx index 6dc806a47..f6c66241f 100644 --- a/web/src/pages/Attachments.tsx +++ b/web/src/pages/Attachments.tsx @@ -36,12 +36,6 @@ const groupAttachmentsByDate = (attachments: Attachment[]): Map { - if (!searchQuery.trim()) return attachments; - const query = searchQuery.toLowerCase(); - return attachments.filter((attachment) => attachment.filename.toLowerCase().includes(query)); -}; - interface AttachmentItemProps { attachment: Attachment; } @@ -74,20 +68,18 @@ const Attachments = observer(() => { const [isLoadingMore, setIsLoadingMore] = useState(false); // Memoized computed values - const filteredAttachments = useMemo(() => filterAttachments(attachments, searchQuery), [attachments, searchQuery]); - - const usedAttachments = useMemo(() => filteredAttachments.filter((attachment) => attachment.memo), [filteredAttachments]); - - const unusedAttachments = useMemo(() => filteredAttachments.filter((attachment) => !attachment.memo), [filteredAttachments]); - + const usedAttachments = useMemo(() => attachments.filter((attachment) => attachment.memo), [attachments]); + const unusedAttachments = useMemo(() => attachments.filter((attachment) => !attachment.memo), [attachments]); const groupedAttachments = useMemo(() => groupAttachmentsByDate(usedAttachments), [usedAttachments]); - // Fetch initial attachments + // Fetch attachments with search filter useEffect(() => { - const fetchInitialAttachments = async () => { + const fetchAttachments = async () => { + loadingState.setLoading(); try { const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({ pageSize: PAGE_SIZE, + filter: searchQuery.trim(), }); setAttachments(fetchedAttachments); setNextPageToken(nextPageToken ?? ""); @@ -99,9 +91,8 @@ const Attachments = observer(() => { } }; - fetchInitialAttachments(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + fetchAttachments(); + }, [searchQuery]); // Load more attachments with pagination const handleLoadMore = useCallback(async () => { @@ -112,6 +103,7 @@ const Attachments = observer(() => { const { attachments: fetchedAttachments, nextPageToken: newPageToken } = await attachmentServiceClient.listAttachments({ pageSize: PAGE_SIZE, pageToken: nextPageToken, + filter: searchQuery.trim(), }); setAttachments((prev) => [...prev, ...fetchedAttachments]); setNextPageToken(newPageToken ?? ""); @@ -121,7 +113,7 @@ const Attachments = observer(() => { } finally { setIsLoadingMore(false); } - }, [nextPageToken, isLoadingMore]); + }, [searchQuery, nextPageToken, isLoadingMore]); // Refetch all attachments from the beginning const handleRefetch = useCallback(async () => { @@ -129,6 +121,7 @@ const Attachments = observer(() => { loadingState.setLoading(); const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({ pageSize: PAGE_SIZE, + filter: searchQuery.trim(), }); setAttachments(fetchedAttachments); setNextPageToken(nextPageToken ?? ""); @@ -138,7 +131,7 @@ const Attachments = observer(() => { loadingState.setError(); toast.error("Failed to refresh attachments. Please try again."); } - }, [loadingState]); + }, [searchQuery, loadingState]); // Delete all unused attachments const handleDeleteUnusedAttachments = useCallback(async () => { @@ -182,7 +175,7 @@ const Attachments = observer(() => { ) : ( <> - {filteredAttachments.length === 0 ? ( + {attachments.length === 0 ? (

{t("message.no-data")}