From c9d44c052685f14929bb2c74ffcc510990f9013a Mon Sep 17 00:00:00 2001 From: gitkeniwo Date: Tue, 18 Nov 2025 00:06:07 +0100 Subject: [PATCH] fix: add load-more button and pagination to attachments page --- web/src/pages/Attachments.tsx | 155 ++++++++++++++++++++++------------ 1 file changed, 99 insertions(+), 56 deletions(-) diff --git a/web/src/pages/Attachments.tsx b/web/src/pages/Attachments.tsx index 7e6d33e40..34e3fea69 100644 --- a/web/src/pages/Attachments.tsx +++ b/web/src/pages/Attachments.tsx @@ -8,6 +8,7 @@ import Empty from "@/components/Empty"; import MobileHeader from "@/components/MobileHeader"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; +import { Button } from "@/components/ui/button"; import { attachmentServiceClient } from "@/grpcweb"; import useLoading from "@/hooks/useLoading"; import useResponsiveWidth from "@/hooks/useResponsiveWidth"; @@ -42,18 +43,51 @@ const Attachments = observer(() => { searchQuery: "", }); const [attachments, setAttachments] = useState([]); + const [nextPageToken, setNextPageToken] = useState(""); + const [isLoadingMore, setIsLoadingMore] = useState(false); const filteredAttachments = attachments.filter((attachment) => includes(attachment.filename, state.searchQuery)); const groupedAttachments = groupAttachmentsByDate(filteredAttachments.filter((attachment) => attachment.memo)); const unusedAttachments = filteredAttachments.filter((attachment) => !attachment.memo); useEffect(() => { - attachmentServiceClient.listAttachments({}).then(({ attachments }) => { - setAttachments(attachments); - loadingState.setFinish(); - Promise.all(attachments.map((attachment) => (attachment.memo ? memoStore.getOrFetchMemoByName(attachment.memo) : null))); - }); + const fetchInitialAttachments = async () => { + try { + const { attachments: fetchedAttachments, nextPageToken } = await attachmentServiceClient.listAttachments({ + pageSize: 50, + }); + setAttachments(fetchedAttachments); + setNextPageToken(nextPageToken ?? ""); + await Promise.all( + fetchedAttachments.map((attachment) => (attachment.memo ? memoStore.getOrFetchMemoByName(attachment.memo) : null)), + ); + } finally { + loadingState.setFinish(); + } + }; + + fetchInitialAttachments(); }, []); + const handleLoadMore = async () => { + if (!nextPageToken) { + return; + } + setIsLoadingMore(true); + try { + const { attachments: fetchedAttachments, nextPageToken: newPageToken } = await attachmentServiceClient.listAttachments({ + pageSize: 50, + pageToken: nextPageToken, + }); + setAttachments((prevAttachments) => [...prevAttachments, ...fetchedAttachments]); + setNextPageToken(newPageToken ?? ""); + await Promise.all( + fetchedAttachments.map((attachment) => (attachment.memo ? memoStore.getOrFetchMemoByName(attachment.memo) : null)), + ); + } finally { + setIsLoadingMore(false); + } + }; + return (
{!md && } @@ -89,61 +123,70 @@ const Attachments = observer(() => {

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

) : ( -
- {Array.from(groupedAttachments.entries()).map(([monthStr, attachments]) => { - return ( -
-
- {dayjs(monthStr).year()} - - {dayjs(monthStr).toDate().toLocaleString(i18n.language, { month: "short" })} - -
-
- {attachments.map((attachment) => { - return ( -
-
- -
-
-

{attachment.filename}

-
-
- ); - })} -
-
- ); - })} - - {unusedAttachments.length > 0 && ( - <> - -
-
-
-
- {t("resource.unused-resources")} - ({unusedAttachments.length}) + <> +
+ {Array.from(groupedAttachments.entries()).map(([monthStr, attachments]) => { + return ( +
+
+ {dayjs(monthStr).year()} + + {dayjs(monthStr).toDate().toLocaleString(i18n.language, { month: "short" })} +
- {unusedAttachments.map((attachment) => { - return ( -
-
- +
+ {attachments.map((attachment) => { + return ( +
+
+ +
+
+

{attachment.filename}

+
-
-

{attachment.filename}

-
-
- ); - })} + ); + })} +
-
- + ); + })} + + {unusedAttachments.length > 0 && ( + <> + +
+
+
+
+ {t("resource.unused-resources")} + ({unusedAttachments.length}) +
+ {unusedAttachments.map((attachment) => { + return ( +
+
+ +
+
+

{attachment.filename}

+
+
+ ); + })} +
+
+ + )} +
+ {nextPageToken && ( +
+ +
)} -
+ )} )}