implement server side attachment filtering

This commit is contained in:
sawatkins 2025-12-05 17:17:22 -08:00
parent 4668c4714b
commit 0669e102d3
2 changed files with 18 additions and 20 deletions

View File

@ -159,6 +159,11 @@ func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAt
Offset: &offset, Offset: &offset,
} }
if request.Filter != "" {
filter := strings.TrimSpace(request.Filter)
findAttachment.FilenameSearch = &filter
}
attachments, err := s.Store.ListAttachments(ctx, findAttachment) attachments, err := s.Store.ListAttachments(ctx, findAttachment)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list attachments: %v", err) return nil, status.Errorf(codes.Internal, "failed to list attachments: %v", err)

View File

@ -36,12 +36,6 @@ const groupAttachmentsByDate = (attachments: Attachment[]): Map<string, Attachme
return grouped; return grouped;
}; };
const filterAttachments = (attachments: Attachment[], searchQuery: string): Attachment[] => {
if (!searchQuery.trim()) return attachments;
const query = searchQuery.toLowerCase();
return attachments.filter((attachment) => attachment.filename.toLowerCase().includes(query));
};
interface AttachmentItemProps { interface AttachmentItemProps {
attachment: Attachment; attachment: Attachment;
} }
@ -74,20 +68,18 @@ const Attachments = observer(() => {
const [isLoadingMore, setIsLoadingMore] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false);
// Memoized computed values // Memoized computed values
const filteredAttachments = useMemo(() => filterAttachments(attachments, searchQuery), [attachments, searchQuery]); const usedAttachments = useMemo(() => attachments.filter((attachment) => attachment.memo), [attachments]);
const unusedAttachments = useMemo(() => attachments.filter((attachment) => !attachment.memo), [attachments]);
const usedAttachments = useMemo(() => filteredAttachments.filter((attachment) => attachment.memo), [filteredAttachments]);
const unusedAttachments = useMemo(() => filteredAttachments.filter((attachment) => !attachment.memo), [filteredAttachments]);
const groupedAttachments = useMemo(() => groupAttachmentsByDate(usedAttachments), [usedAttachments]); const groupedAttachments = useMemo(() => groupAttachmentsByDate(usedAttachments), [usedAttachments]);
// Fetch initial attachments // Fetch attachments with search filter
useEffect(() => { useEffect(() => {
const fetchInitialAttachments = async () => { const fetchAttachments = async () => {
loadingState.setLoading();
try { try {
const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({ const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
filter: searchQuery.trim(),
}); });
setAttachments(fetchedAttachments); setAttachments(fetchedAttachments);
setNextPageToken(nextPageToken ?? ""); setNextPageToken(nextPageToken ?? "");
@ -99,9 +91,8 @@ const Attachments = observer(() => {
} }
}; };
fetchInitialAttachments(); fetchAttachments();
// eslint-disable-next-line react-hooks/exhaustive-deps }, [searchQuery]);
}, []);
// Load more attachments with pagination // Load more attachments with pagination
const handleLoadMore = useCallback(async () => { const handleLoadMore = useCallback(async () => {
@ -112,6 +103,7 @@ const Attachments = observer(() => {
const { attachments: fetchedAttachments, nextPageToken: newPageToken } = await attachmentServiceClient.listAttachments({ const { attachments: fetchedAttachments, nextPageToken: newPageToken } = await attachmentServiceClient.listAttachments({
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
pageToken: nextPageToken, pageToken: nextPageToken,
filter: searchQuery.trim(),
}); });
setAttachments((prev) => [...prev, ...fetchedAttachments]); setAttachments((prev) => [...prev, ...fetchedAttachments]);
setNextPageToken(newPageToken ?? ""); setNextPageToken(newPageToken ?? "");
@ -121,7 +113,7 @@ const Attachments = observer(() => {
} finally { } finally {
setIsLoadingMore(false); setIsLoadingMore(false);
} }
}, [nextPageToken, isLoadingMore]); }, [searchQuery, nextPageToken, isLoadingMore]);
// Refetch all attachments from the beginning // Refetch all attachments from the beginning
const handleRefetch = useCallback(async () => { const handleRefetch = useCallback(async () => {
@ -129,6 +121,7 @@ const Attachments = observer(() => {
loadingState.setLoading(); loadingState.setLoading();
const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({ const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
filter: searchQuery.trim(),
}); });
setAttachments(fetchedAttachments); setAttachments(fetchedAttachments);
setNextPageToken(nextPageToken ?? ""); setNextPageToken(nextPageToken ?? "");
@ -138,7 +131,7 @@ const Attachments = observer(() => {
loadingState.setError(); loadingState.setError();
toast.error("Failed to refresh attachments. Please try again."); toast.error("Failed to refresh attachments. Please try again.");
} }
}, [loadingState]); }, [searchQuery, loadingState]);
// Delete all unused attachments // Delete all unused attachments
const handleDeleteUnusedAttachments = useCallback(async () => { const handleDeleteUnusedAttachments = useCallback(async () => {
@ -182,7 +175,7 @@ const Attachments = observer(() => {
</div> </div>
) : ( ) : (
<> <>
{filteredAttachments.length === 0 ? ( {attachments.length === 0 ? (
<div className="w-full mt-8 mb-8 flex flex-col justify-center items-center italic"> <div className="w-full mt-8 mb-8 flex flex-col justify-center items-center italic">
<Empty /> <Empty />
<p className="mt-4 text-muted-foreground">{t("message.no-data")}</p> <p className="mt-4 text-muted-foreground">{t("message.no-data")}</p>