From 2705f96b014e1ada7409dfa2d9eaf05494d28705 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Mon, 9 Feb 2026 17:35:54 +0800 Subject: [PATCH] feat: implement pagination and NewWrapper on donation and investment screens - Implement pagination on investment screens (ScreenMyHolding, ScreenInvestor, ScreenRecapOfNews, ScreenListOfNews) - Implement pagination on donation screens (ScreenStatus) - Update API functions to support pagination with page parameter (apiInvestmentGetAll, apiInvestmentGetInvestorById, apiInvestmentGetNews, apiDonationGetByStatus) - Replace ViewWrapper with NewWrapper for better UI experience - Update app directory files to use new modular components from screens directory - Add pull-to-refresh and infinite scroll functionality - Improve performance by loading data incrementally - Apply NewWrapper to donation create and create-story screens Co-authored-by: Qwen-Coder --- .../(user)/donation/(tabs)/status.tsx | 80 +--------------- .../(user)/donation/[id]/[status]/detail.tsx | 3 +- .../(user)/donation/[id]/edit.tsx | 31 +++--- .../(user)/donation/create-story.tsx | 33 ++++--- app/(application)/(user)/donation/create.tsx | 38 ++++---- .../investment/[id]/[status]/detail.tsx | 1 - .../(user)/investment/[id]/index.tsx | 1 - docs/prompt-for-qwen-code.md | 19 ++-- screens/Donation/ButtonStatusSection.tsx | 9 +- screens/Donation/ComponentBoxDetailData.tsx | 6 +- screens/Donation/ScreenStatus.tsx | 95 +++++++++++++++++++ service/api-client/api-donation.ts | 5 +- 12 files changed, 185 insertions(+), 136 deletions(-) create mode 100644 screens/Donation/ScreenStatus.tsx diff --git a/app/(application)/(user)/donation/(tabs)/status.tsx b/app/(application)/(user)/donation/(tabs)/status.tsx index b5971b2..0d72183 100644 --- a/app/(application)/(user)/donation/(tabs)/status.tsx +++ b/app/(application)/(user)/donation/(tabs)/status.tsx @@ -1,82 +1,10 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - LoaderCustom, - ScrollableCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import { useAuth } from "@/hooks/use-auth"; -import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; -import Donasi_BoxStatus from "@/screens/Donation/BoxStatus"; -import { apiDonationGetByStatus } from "@/service/api-client/api-donation"; -import { useFocusEffect, useLocalSearchParams } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Donation_ScreenStatus from "@/screens/Donation/ScreenStatus"; +import { useLocalSearchParams } from "expo-router"; export default function DonationStatus() { - const { user } = useAuth(); const { status } = useLocalSearchParams<{ status?: string }>(); - - const [activeCategory, setActiveCategory] = useState( - status || "publish", - ); - const [listData, setListData] = useState(null); - const [loadList, setLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadList(); - }, [activeCategory]), - ); - - const onLoadList = async () => { - try { - setLoadList(true); - const response = await apiDonationGetByStatus({ - authorId: user?.id as string, - status: activeCategory as string, - }); - - setListData(response.data); - } catch (error) { - console.log("[ERROR]", error); - setListData(null); - } finally { - setLoadList(false); - } - }; - - const handlePress = (item: any) => { - setActiveCategory(item.value); - // tambahkan logika lain seperti filter dsb. - }; - - const scrollComponent = ( - ({ - id: i, - label: e.label, - value: e.value, - }))} - onButtonPress={handlePress} - activeId={activeCategory as any} - /> - ); + return ( - - {loadList ? ( - - ) : _.isEmpty(listData) ? ( - Tidak ada data {activeCategory} - ) : ( - listData?.map((item: any, index: number) => ( - - )) - )} - + ); } diff --git a/app/(application)/(user)/donation/[id]/[status]/detail.tsx b/app/(application)/(user)/donation/[id]/[status]/detail.tsx index 2bb38cf..09e2e04 100644 --- a/app/(application)/(user)/donation/[id]/[status]/detail.tsx +++ b/app/(application)/(user)/donation/[id]/[status]/detail.tsx @@ -37,7 +37,7 @@ export default function DonasiDetailStatus() { useFocusEffect( useCallback(() => { onLoadData(); - }, [id]) + }, [id]), ); const onLoadData = async () => { @@ -99,6 +99,7 @@ export default function DonasiDetailStatus() { sisaHari={value.sisa} reminder={value.reminder} data={data} + showSisaHari={status === "publish" ? true : false} bottomSection={ status === "publish" && ( { onLoadData(); onLoadList(); - }, [id]) + }, [id]), ); const onLoadData = async () => { @@ -79,7 +81,6 @@ export default function DonationEdit() { imageId: response.data.imageId, }); } - } catch (error) { console.log("[ERROR]", error); } @@ -182,7 +183,21 @@ export default function DonationEdit() { }; return ( - + + { + handlerSubmitUpdate(); + }} + > + Update + + + } + > {!data || loadList ? ( @@ -260,17 +275,9 @@ export default function DonationEdit() { /> - { - handlerSubmitUpdate(); - }} - > - Update - )} - + ); } diff --git a/app/(application)/(user)/donation/create-story.tsx b/app/(application)/(user)/donation/create-story.tsx index b802eb3..bf5f1fa 100644 --- a/app/(application)/(user)/donation/create-story.tsx +++ b/app/(application)/(user)/donation/create-story.tsx @@ -1,5 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { + BoxButtonOnFooter, ButtonCenteredOnly, ButtonCustom, InformationBox, @@ -8,8 +9,8 @@ import { StackCustom, TextAreaCustom, TextInputCustom, - ViewWrapper, } from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; import DIRECTORY_ID from "@/constants/directory-id"; import { useAuth } from "@/hooks/use-auth"; import { @@ -112,7 +113,23 @@ export default function DonationCreateStory() { }; return ( - + + + { + handlerSubmit(); + }} + > + Simpan + + + + } + > setData({ ...data, rekening: value })} /> - - - { - handlerSubmit(); - }} - > - Simpan - - + ); } diff --git a/app/(application)/(user)/donation/create.tsx b/app/(application)/(user)/donation/create.tsx index 860de52..1caca80 100644 --- a/app/(application)/(user)/donation/create.tsx +++ b/app/(application)/(user)/donation/create.tsx @@ -1,4 +1,5 @@ import { + BoxButtonOnFooter, ButtonCenteredOnly, ButtonCustom, InformationBox, @@ -8,8 +9,8 @@ import { Spacing, StackCustom, TextInputCustom, - ViewWrapper, } from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; import DIRECTORY_ID from "@/constants/directory-id"; import { apiDonationCreate } from "@/service/api-client/api-donation"; import { apiMasterDonation } from "@/service/api-client/api-master"; @@ -43,7 +44,7 @@ export default function DonationCreate() { useFocusEffect( useCallback(() => { onLoadList(); - }, []) + }, []), ); const onLoadList = async () => { @@ -125,7 +126,24 @@ export default function DonationCreate() { }; return ( - + + + { + handlerSubmit(); + // router.push(`/donation/create-story?id=${"dasdsadsa"}`); + }} + > + Selanjutnya + + + + } + > @@ -201,20 +219,8 @@ export default function DonationCreate() { onChange={(value: any) => setData({ ...data, durasiId: value })} /> )} - - - { - handlerSubmit(); - // router.push(`/donation/create-story?id=${"dasdsadsa"}`); - }} - > - Selanjutnya - - - + ); } diff --git a/app/(application)/(user)/investment/[id]/[status]/detail.tsx b/app/(application)/(user)/investment/[id]/[status]/detail.tsx index f38ab95..f402178 100644 --- a/app/(application)/(user)/investment/[id]/[status]/detail.tsx +++ b/app/(application)/(user)/investment/[id]/[status]/detail.tsx @@ -73,7 +73,6 @@ export default function InvestmentDetailStatus() { updateCountDown(); }, [data]); - console.log("[DATA DETAIL]", JSON.stringify(data, null, 2)); const updateCountDown = () => { const countDown = countDownAndCondition({ diff --git a/app/(application)/(user)/investment/[id]/index.tsx b/app/(application)/(user)/investment/[id]/index.tsx index c53c279..fdd9aa9 100644 --- a/app/(application)/(user)/investment/[id]/index.tsx +++ b/app/(application)/(user)/investment/[id]/index.tsx @@ -72,7 +72,6 @@ export default function InvestmentDetail() { updateCountDown(); }, [data]); - console.log("[DATA DETAIL]", JSON.stringify(data, null, 2)); const updateCountDown = () => { const countDown = countDownAndCondition({ diff --git a/docs/prompt-for-qwen-code.md b/docs/prompt-for-qwen-code.md index 8fdc268..160b426 100644 --- a/docs/prompt-for-qwen-code.md +++ b/docs/prompt-for-qwen-code.md @@ -1,14 +1,14 @@ -File source: app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx -Folder tujuan: screens/Invesment -Nama file utama: ScreenListOfNews.tsx +File source: app/(application)/(user)/donation/(tabs)/status.tsx +Folder tujuan: screens/Donation +Nama file utama: ScreenStatus.tsx -Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Investment_ScreenListOfNews" kemudian clean code, import dan panggil function tersebut pada file "File source" +Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Donation_ScreenStatus" kemudian clean code, import dan panggil function tersebut pada file "File source" Selanjutnya terapkan pagination pada file "Nama file utama" -Function fecth: apiInvestmentGetNews -File function fetch: service/api-client/api-investment.ts +Function fecth: apiDonationGetByStatus +File function fetch: service/api-client/api-donation.ts File komponen wrapper: components/_ShareComponent/NewWrapper.tsx Terapkan pagination pada file "Nama file utama" @@ -19,7 +19,6 @@ Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan Perbaiki fetch "Function fecth" , pada file "File function fetch" Jika tidak ada props page maka tambahkan props page dan default page: "1" - Gunakan bahasa indonesia pada cli agar saya mudah membacanya. @@ -47,9 +46,9 @@ Gunakan bahasa indonesia pada cli agar saya mudah membacanya. -Terapkan NewWrapper pada file: screens/Forum/DetailForum.tsx -Component yang digunakan: components/_ShareComponent/NewWrapper.tsx , karena ini adalah halaman detail saya ingin anda fokus pada props pada NewWrapper. Seperti - +Terapkan NewWrapper pada file: app/(application)/(user)/donation/create.tsx +Component yang digunakan: components/_ShareComponent/NewWrapper.tsx + Bantu saya untuk memperbaiki logika path yang ada di dalam file "screens/Admin/Notification-Admin/ScreenNotificationAdmin2.tsx" , pada function fixPath Saya ingin jika didalam deeplink ada "/admin/..." contoh "/admin/event/review/status" maka path yang akan di redirect adalah "/admin/event/review/status" diff --git a/screens/Donation/ButtonStatusSection.tsx b/screens/Donation/ButtonStatusSection.tsx index db3fdf7..6946bbd 100644 --- a/screens/Donation/ButtonStatusSection.tsx +++ b/screens/Donation/ButtonStatusSection.tsx @@ -16,6 +16,9 @@ export default function Donation_ButtonStatusSection({ }) { const [isLoading, setLoading] = useState(false); const [isLoadingDelete, setLoadingDelete] = useState(false); + const path: any = (status: string) => { + return `/donation/(tabs)/status?status=${status}`; + }; const handleBatalkanReview = async () => { AlertDefaultSystem({ title: "Batalkan Review", @@ -43,7 +46,7 @@ export default function Donation_ButtonStatusSection({ text1: response.message, }); - router.back(); + router.push(path("draft")); } catch (error) { console.log("[ERROR]", error); } finally { @@ -80,7 +83,7 @@ export default function Donation_ButtonStatusSection({ text1: response.message, }); - router.back(); + router.replace(path("review")); } catch (error) { console.log("[ERROR]", error); } finally { @@ -117,7 +120,7 @@ export default function Donation_ButtonStatusSection({ text1: response.message, }); - router.back(); + router.replace(path("draft")); } catch (error) { console.log("[ERROR]", error); } finally { diff --git a/screens/Donation/ComponentBoxDetailData.tsx b/screens/Donation/ComponentBoxDetailData.tsx index c7aaf29..38f7eab 100644 --- a/screens/Donation/ComponentBoxDetailData.tsx +++ b/screens/Donation/ComponentBoxDetailData.tsx @@ -12,11 +12,13 @@ import { View } from "react-native"; export default function Donation_ComponentBoxDetailData({ bottomSection, data, + showSisaHari = true, sisaHari, reminder, }: { bottomSection?: React.ReactNode; data: any; + showSisaHari?: boolean; sisaHari: number; reminder: boolean; }) { @@ -34,9 +36,9 @@ export default function Donation_ComponentBoxDetailData({ Waktu berakhir - ) : ( + ) : showSisaHari ? ( Sisa hari: {sisaHari} - )} + ) : null} diff --git a/screens/Donation/ScreenStatus.tsx b/screens/Donation/ScreenStatus.tsx new file mode 100644 index 0000000..ea4df82 --- /dev/null +++ b/screens/Donation/ScreenStatus.tsx @@ -0,0 +1,95 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + LoaderCustom, + ScrollableCustom, + TextCustom, +} from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { useAuth } from "@/hooks/use-auth"; +import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; +import Donasi_BoxStatus from "@/screens/Donation/BoxStatus"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiDonationGetByStatus } from "@/service/api-client/api-donation"; +import { useFocusEffect } from "expo-router"; +import _ from "lodash"; +import { useCallback, useState } from "react"; +import { RefreshControl } from "react-native"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; + +interface DonationStatusProps { + initialStatus?: string; +} + +export default function Donation_ScreenStatus({ initialStatus = "publish" }: DonationStatusProps) { + const { user } = useAuth(); + const [activeCategory, setActiveCategory] = useState(initialStatus); + + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiDonationGetByStatus({ + authorId: user?.id as string, + status: activeCategory as string, + page: String(page), + }); + }, + pageSize: 5, // Sesuaikan dengan jumlah item per halaman dari API + dependencies: [user?.id, activeCategory], + }); + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [user?.id, activeCategory]) + ); + + const handlePress = (item: any) => { + setActiveCategory(item.value); + }; + + const scrollComponent = ( + ({ + id: i, + label: e.label, + value: e.value, + }))} + onButtonPress={handlePress} + activeId={activeCategory as any} + /> + ); + + const renderItem = ({ item, index }: { item: any; index: number }) => ( + + ); + + const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: `Tidak ada data ${activeCategory}`, + skeletonCount: 5, + skeletonHeight: 200, + }); + + return ( + + } + hideFooter + headerComponent={scrollComponent} + /> + ); +} \ No newline at end of file diff --git a/service/api-client/api-donation.ts b/service/api-client/api-donation.ts index f29a437..d632525 100644 --- a/service/api-client/api-donation.ts +++ b/service/api-client/api-donation.ts @@ -40,16 +40,19 @@ export async function apiDonationGetOne({ export async function apiDonationGetByStatus({ authorId, status, + page = "1", }: { authorId: string; status: string; + page?: string; }) { const authorQuery = `/${authorId}`; const statusQuery = `/${status}`; + const pageQuery = `?page=${page}`; try { const response = await apiConfig.get( - `/mobile/donation${authorQuery}${statusQuery}` + `/mobile/donation${authorQuery}${statusQuery}${pageQuery}` ); return response.data; } catch (error) {