From b2be7be5339b3cb74c931cdd342fd021cc55b73e Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Tue, 10 Feb 2026 17:30:30 +0800 Subject: [PATCH] Saya telah melakukan serangkaian perubahan penting dalam pengembangan aplikasi HIPMI Mobile, khususnya dalam modul Donasi. Berikut adalah ringkasan perubahan yang telah dilakukan: 1. Menerapkan sistem pagination pada berbagai komponen layar donasi: - ScreenBeranda.tsx - ScreenMyDonation.tsx - ScreenRecapOfNews.tsx - ScreenListOfNews.tsx - ScreenListOfDonatur.tsx - ScreenFundDisbursement.tsx 2. Memperbarui fungsi-fungsi API untuk mendukung parameter page: - apiDonationGetAll - apiDonationGetNewsById - apiDonationListOfDonaturById - apiDonationDisbursementOfFundsListById 3. Mengganti komponen wrapper lama (ViewWrapper) dengan NewWrapper yang mendukung sistem pagination 4. Membuat komponen layar terpisah untuk meningkatkan modularitas kode 5. Memperbaiki berbagai error yang terjadi, termasuk masalah dengan import komponen dan struktur JSX ### No Issue --- .../(user)/donation/(tabs)/index.tsx | 55 +----- .../(user)/donation/(tabs)/my-donation.tsx | 147 +-------------- .../donation/[id]/(news)/[news]/edit-news.tsx | 28 +-- .../(user)/donation/[id]/(news)/add-news.tsx | 32 ++-- .../donation/[id]/(news)/list-of-news.tsx | 110 +---------- .../donation/[id]/(news)/recap-of-news.tsx | 112 +---------- .../[invoiceId]/invoice.tsx | 30 +-- .../(user)/donation/[id]/[status]/detail.tsx | 85 ++++++--- .../(user)/donation/[id]/edit.tsx | 3 +- .../donation/[id]/fund-disbursement.tsx | 124 +------------ .../(user)/donation/[id]/index.tsx | 47 +++-- .../(user)/donation/[id]/list-of-donatur.tsx | 96 +--------- app/(application)/(user)/home.tsx | 11 +- docs/prompt-for-qwen-code.md | 54 ++++-- screens/Donation/BoxNews.tsx | 21 +++ screens/Donation/ScreenBeranda.tsx | 62 +++++++ screens/Donation/ScreenFundDisbursement.tsx | 174 ++++++++++++++++++ screens/Donation/ScreenListOfDonatur.tsx | 98 ++++++++++ screens/Donation/ScreenListOfNews.tsx | 97 ++++++++++ screens/Donation/ScreenMyDonation.tsx | 152 +++++++++++++++ screens/Donation/ScreenRecapOfNews.tsx | 105 +++++++++++ screens/Donation/ScreenStatus.tsx | 60 +++--- service/api-admin/api-admin-donation.ts | 20 +- service/api-client/api-donation.ts | 28 ++- utils/formatChatTime.ts | 2 +- 25 files changed, 987 insertions(+), 766 deletions(-) create mode 100644 screens/Donation/BoxNews.tsx create mode 100644 screens/Donation/ScreenBeranda.tsx create mode 100644 screens/Donation/ScreenFundDisbursement.tsx create mode 100644 screens/Donation/ScreenListOfDonatur.tsx create mode 100644 screens/Donation/ScreenListOfNews.tsx create mode 100644 screens/Donation/ScreenMyDonation.tsx create mode 100644 screens/Donation/ScreenRecapOfNews.tsx diff --git a/app/(application)/(user)/donation/(tabs)/index.tsx b/app/(application)/(user)/donation/(tabs)/index.tsx index eab5fb2..f7abd66 100644 --- a/app/(application)/(user)/donation/(tabs)/index.tsx +++ b/app/(application)/(user)/donation/(tabs)/index.tsx @@ -1,56 +1,9 @@ -import { - FloatingButton, - LoaderCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import Donation_BoxPublish from "@/screens/Donation/BoxPublish"; -import { apiDonationGetAll } from "@/service/api-client/api-donation"; -import { router, useFocusEffect } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Donation_ScreenBeranda from "@/screens/Donation/ScreenBeranda"; export default function DonationBeranda() { - const [list, setList] = useState(null); - const [loadList, setLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, []) - ); - - const onLoadData = async () => { - try { - setLoadList(true); - const response = await apiDonationGetAll({ - category: "beranda" - }); - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadList(false); - } - }; - return ( - router.push("/donation/create")} /> - } - > - {loadList ? ( - - ) : _.isEmpty(list) ? ( - Belum ada donasi - ) : ( - list?.map((item: any, index: number) => ( - - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/donation/(tabs)/my-donation.tsx b/app/(application)/(user)/donation/(tabs)/my-donation.tsx index d7ec173..3faf051 100644 --- a/app/(application)/(user)/donation/(tabs)/my-donation.tsx +++ b/app/(application)/(user)/donation/(tabs)/my-donation.tsx @@ -1,148 +1,5 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - BadgeCustom, - BaseBox, - DummyLandscapeImage, - Grid, - LoaderCustom, - StackCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import { useAuth } from "@/hooks/use-auth"; -import { apiDonationGetAll } from "@/service/api-client/api-donation"; -import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; -import { Href, router, useFocusEffect } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; -import { View } from "react-native"; -import Toast from "react-native-toast-message"; +import Donation_ScreenMyDonation from "@/screens/Donation/ScreenMyDonation"; export default function DonationMyDonation() { - const { user } = useAuth(); - const [list, setList] = useState(null); - const [loadList, setLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, [user?.id]), - ); - - const onLoadData = async () => { - if (!user?.id) { - Toast.show({ - type: "error", - text1: "Load data gagal, user tidak ditemukan", - }); - return; - } - - try { - setLoadList(true); - const response = await apiDonationGetAll({ - category: "my-donation", - authorId: user?.id, - }); - - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadList(false); - } - }; - - const handlerColor = (status: string) => { - if (status === "menunggu") { - return "orange"; - } else if (status === "proses") { - return "white"; - } else if (status === "berhasil") { - return "green"; - } else if (status === "gagal") { - return "red"; - } - }; - - const handlePress = ({ - invoiceId, - donationId, - status, - }: { - invoiceId: string; - donationId: string; - status: string; - }) => { - const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`; - if (status === "menunggu") { - router.push(`${url}/invoice`); - } else if (status === "proses") { - router.push(`${url}/process`); - } else if (status === "berhasil") { - router.push(`${url}/success`); - } else if (status === "gagal") { - router.push(`${url}/failed`); - } - }; - - return ( - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Belum ada transaksi - - ) : ( - list?.map((item, index) => ( - { - handlePress({ - status: _.lowerCase(item.statusInvoice), - invoiceId: item.id, - donationId: item.donasiId, - }); - }} - > - - - - - - - - - - - {item.title || "-"} - - - - Rp. {formatCurrencyDisplay(item.nominal)} - - - - {item.statusInvoice} - - - - - - )) - )} - - ); + return ; } diff --git a/app/(application)/(user)/donation/[id]/(news)/[news]/edit-news.tsx b/app/(application)/(user)/donation/[id]/(news)/[news]/edit-news.tsx index ed2ed10..78094fa 100644 --- a/app/(application)/(user)/donation/[id]/(news)/[news]/edit-news.tsx +++ b/app/(application)/(user)/donation/[id]/(news)/[news]/edit-news.tsx @@ -1,5 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { + BoxButtonOnFooter, ButtonCenteredOnly, ButtonCustom, InformationBox, @@ -31,7 +32,7 @@ export default function DonationEditNews() { useFocusEffect( useCallback(() => { onLoadData(); - }, [news]) + }, [news]), ); const onLoadData = async () => { @@ -104,7 +105,21 @@ export default function DonationEditNews() { }; return ( - + + { + handlerSubmitUpdate(); + }} + > + Update + + + } + > - { - handlerSubmitUpdate(); - }} - > - Update - diff --git a/app/(application)/(user)/donation/[id]/(news)/add-news.tsx b/app/(application)/(user)/donation/[id]/(news)/add-news.tsx index fc89f92..b70391c 100644 --- a/app/(application)/(user)/donation/[id]/(news)/add-news.tsx +++ b/app/(application)/(user)/donation/[id]/(news)/add-news.tsx @@ -1,8 +1,10 @@ import { + BoxButtonOnFooter, ButtonCenteredOnly, ButtonCustom, InformationBox, LandscapeFrameUploaded, + NewWrapper, Spacing, StackCustom, TextAreaCustom, @@ -53,7 +55,7 @@ export default function DonationAddNews() { text1: "Gagal menambah berita", }); - return + return; } Toast.show({ @@ -70,7 +72,21 @@ export default function DonationAddNews() { }; return ( - + + { + handlerSubmit(); + }} + > + Simpan + + + } + > @@ -116,17 +132,7 @@ export default function DonationAddNews() { /> - { - handlerSubmit(); - }} - > - Simpan - - - + ); } diff --git a/app/(application)/(user)/donation/[id]/(news)/list-of-news.tsx b/app/(application)/(user)/donation/[id]/(news)/list-of-news.tsx index c7471ef..171bc89 100644 --- a/app/(application)/(user)/donation/[id]/(news)/list-of-news.tsx +++ b/app/(application)/(user)/donation/[id]/(news)/list-of-news.tsx @@ -1,110 +1,8 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - BackButton, - BaseBox, - DrawerCustom, - Grid, - LoaderCustom, - MenuDrawerDynamicGrid, - TextCustom, - ViewWrapper, -} from "@/components"; -import { IconPlus } from "@/components/_Icon"; -import { apiDonationGetNewsById } from "@/service/api-client/api-donation"; -import { formatChatTime } from "@/utils/formatChatTime"; -import { - router, - Stack, - useFocusEffect, - useLocalSearchParams, -} from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import { useLocalSearchParams } from "expo-router"; +import Donation_ScreenListOfNews from "@/screens/Donation/ScreenListOfNews"; export default function DonationRecapOfNews() { const { id } = useLocalSearchParams(); - const [openDrawer, setOpenDrawer] = useState(false); - const [list, setList] = useState(null); - const [loadList, setLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadList(); - }, [id]) - ); - - const onLoadList = async () => { - try { - setLoadList(true); - const response = await apiDonationGetNewsById({ - id: id as string, - category: "get-all", - }); - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - setList([]); - } finally { - setLoadList(false); - } - }; - - return ( - <> - , - }} - /> - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Tidak ada kabar - - ) : ( - list?.map((item: any, index: number) => ( - - - - - {item?.title || "-"} - - - - - {formatChatTime(item?.createdAt)} - - - - - )) - )} - - - setOpenDrawer(false)} - height={"auto"} - > - , - label: "Tambah Berita", - path: `/donation/${id}/(news)/add-news`, - }, - ]} - onPressItem={(item) => { - console.log("PATH ", item.path); - router.navigate(item.path as any); - setOpenDrawer(false); - }} - /> - - - ); + + return ; } diff --git a/app/(application)/(user)/donation/[id]/(news)/recap-of-news.tsx b/app/(application)/(user)/donation/[id]/(news)/recap-of-news.tsx index b27f75a..78afe53 100644 --- a/app/(application)/(user)/donation/[id]/(news)/recap-of-news.tsx +++ b/app/(application)/(user)/donation/[id]/(news)/recap-of-news.tsx @@ -1,112 +1,8 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - BackButton, - BaseBox, - DotButton, - DrawerCustom, - Grid, - LoaderCustom, - MenuDrawerDynamicGrid, - TextCustom, - ViewWrapper, -} from "@/components"; -import { IconPlus } from "@/components/_Icon"; -import { apiDonationGetNewsById } from "@/service/api-client/api-donation"; -import { formatChatTime } from "@/utils/formatChatTime"; -import { - router, - Stack, - useFocusEffect, - useLocalSearchParams, -} from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import { useLocalSearchParams } from "expo-router"; +import Donation_ScreenRecapOfNews from "@/screens/Donation/ScreenRecapOfNews"; export default function DonationRecapOfNews() { const { id } = useLocalSearchParams(); - const [openDrawer, setOpenDrawer] = useState(false); - const [list, setList] = useState(null); - const [loadList, setLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadList(); - }, [id]) - ); - - const onLoadList = async () => { - try { - setLoadList(true); - const response = await apiDonationGetNewsById({ - id: id as string, - category: "get-all", - }); - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - setList([]); - } finally { - setLoadList(false); - } - }; - - return ( - <> - , - headerRight: () => setOpenDrawer(true)} />, - }} - /> - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Tidak ada kabar - - ) : ( - list?.map((item: any, index: number) => ( - - - - - {item?.title || "-"} - - - - - {formatChatTime(item?.createdAt)} - - - - - )) - )} - - - setOpenDrawer(false)} - height={"auto"} - > - , - label: "Tambah Berita", - path: `/donation/${id}/(news)/add-news`, - }, - ]} - onPressItem={(item) => { - console.log("PATH ", item.path); - router.navigate(item.path as any); - setOpenDrawer(false); - }} - /> - - - ); + + return ; } diff --git a/app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/invoice.tsx b/app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/invoice.tsx index 4b51b55..943ec6d 100644 --- a/app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/invoice.tsx +++ b/app/(application)/(user)/donation/[id]/(transaction-flow)/[invoiceId]/invoice.tsx @@ -1,6 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { BaseBox, + BoxButtonOnFooter, ButtonCenteredOnly, ButtonCustom, Grid, @@ -35,7 +36,7 @@ export default function DonationInvoice() { useFocusEffect( useCallback(() => { onLoadData(); - }, [invoiceId]) + }, [invoiceId]), ); const onLoadData = async () => { @@ -100,7 +101,22 @@ export default function DonationInvoice() { return ( <> - + + { + handlerUpdateInvoice(); + }} + > + Simpan + + + } + > - - { - handlerUpdateInvoice(); - }} - > - Simpan - diff --git a/app/(application)/(user)/donation/[id]/[status]/detail.tsx b/app/(application)/(user)/donation/[id]/[status]/detail.tsx index 09e2e04..8f94345 100644 --- a/app/(application)/(user)/donation/[id]/[status]/detail.tsx +++ b/app/(application)/(user)/donation/[id]/[status]/detail.tsx @@ -4,11 +4,12 @@ import { DotButton, DrawerCustom, MenuDrawerDynamicGrid, + NewWrapper, Spacing, - ViewWrapper, } from "@/components"; import { IconEdit, IconNews } from "@/components/_Icon"; import { IMenuDrawerItem } from "@/components/_Interface/types"; +import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom"; import { MainColor } from "@/constants/color-palet"; import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import Donation_ButtonStatusSection from "@/screens/Donation/ButtonStatusSection"; @@ -26,13 +27,14 @@ import { } from "expo-router"; import _ from "lodash"; import { useCallback, useEffect, useState } from "react"; +import { RefreshControl } from "react-native"; export default function DonasiDetailStatus() { const { id, status } = useLocalSearchParams(); const [openDrawer, setOpenDrawer] = useState(false); const [openDrawerPublish, setOpenDrawerPublish] = useState(false); - - const [data, setData] = useState(); + const [refreshing, setRefreshing] = useState(false); + const [data, setData] = useState(null); useFocusEffect( useCallback(() => { @@ -80,6 +82,17 @@ export default function DonasiDetailStatus() { }); }; + const onRefresh = useCallback(() => { + try { + setRefreshing(true); + onLoadData(); + } catch (error) { + console.log("Error refresh"); + } finally { + setRefreshing(false); + } + }, []); + return ( <> - - + } + > + {!data ? ( + + ) : ( + <> + + ) + } + /> + + + {data && ( + - ) - } - /> - - - - - + )} + + + + )} + {!data || loadList ? ( - + ) : ( (null); - const [loadData, setLoadData] = React.useState(false); - - useFocusEffect( - React.useCallback(() => { - onLoadData(); - }, [id]) - ); - - const onLoadData = async () => { - try { - setLoadData(true); - - const responseData = await apiDonationGetOne({ - id: id as string, - category: "permanent", - }); - - if (responseData.success) { - setData({ - totalPencairan: responseData.data.totalPencairan, - akumulasiPencairan: responseData.data.akumulasiPencairan, - }); - } - - const responseList = await apiDonationDisbursementOfFundsListById({ - id: id as string, - }); - - if (responseList.success) { - setListData(responseList.data); - } - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadData(false); - } - }; - - return ( - <> - - - - - - - Rp. {formatCurrencyDisplay(data?.totalPencairan)} - - Total Pencairan Dana - - - - {data?.akumulasiPencairan} kali - - Akumulasi Pencairan - - - - - {loadData ? ( - - ) : _.isEmpty(listData) ? ( - - Belum ada data - - ) : ( - listData?.map((item, index) => ( - - - - - {item?.title} - - - {dayjs(item?.createdAt).format("DD MMM YYYY")} - - - {item?.deskripsi} - { - router.navigate(`/(application)/(image)/preview-image/${item?.imageId}`); - }} - icon="file-text" - > - Bukti Transaksi - - - - )) - )} - - - ); + + return ; } diff --git a/app/(application)/(user)/donation/[id]/index.tsx b/app/(application)/(user)/donation/[id]/index.tsx index 3203738..4bf17a9 100644 --- a/app/(application)/(user)/donation/[id]/index.tsx +++ b/app/(application)/(user)/donation/[id]/index.tsx @@ -6,10 +6,12 @@ import { DotButton, DrawerCustom, MenuDrawerDynamicGrid, + NewWrapper, StackCustom, ViewWrapper, } from "@/components"; import { IconNews } from "@/components/_Icon"; +import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom"; import { useAuth } from "@/hooks/use-auth"; import Donation_ComponentBoxDetailData from "@/screens/Donation/ComponentBoxDetailData"; import Donation_ComponentInfoFundrising from "@/screens/Donation/ComponentInfoFundrising"; @@ -34,7 +36,7 @@ export default function DonasiDetailBeranda() { useFocusEffect( useCallback(() => { onLoadData(); - }, [id]) + }, [id]), ); const onLoadData = async () => { @@ -75,10 +77,10 @@ export default function DonasiDetailBeranda() { <> router.navigate(`/donation/${id}/(transaction-flow)`)} > - {value?.reminder ? "Waktu berakhir" : "Donasi"} + {!data ? "Loading..." : value?.reminder ? "Waktu berakhir" : "Donasi"} @@ -96,21 +98,30 @@ export default function DonasiDetailBeranda() { ) : null, }} /> - - - } - /> - - - - + + {!data ? ( + + ) : ( + + + } + /> + + + + )} + (null); - const [loadData, setLoadData] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, [id]) - ); - - const onLoadData = async () => { - try { - setLoadData(true); - const response = await apiAdminDonationListOfDonaturById({ - id: id as string, - }); - - - if (response.success) { - setListData(response.data); - } - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadData(false); - } - }; - - return ( - <> - - {loadData ? ( - - ) : _.isEmpty(listData) ? ( - - Belum ada donatur - - ) : ( - listData?.map((item: any, index: number) => ( - - - - - - - - {item?.Author?.username || "-"} - - - - Berdonas sebesar - - Rp. {formatCurrencyDisplay(item?.nominal)} - - - {dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")} - - - - - - )) - )} - - - ); + + return ; } diff --git a/app/(application)/(user)/home.tsx b/app/(application)/(user)/home.tsx index 95cf893..e964490 100644 --- a/app/(application)/(user)/home.tsx +++ b/app/(application)/(user)/home.tsx @@ -29,14 +29,14 @@ export default function Application() { checkVersion(); userData(token as string); syncUnreadCount(); - }, [user?.id, token]) + }, [user?.id, token]), ); async function onLoadData() { const response = await apiUser(user?.id as string); console.log( "[Profile ID]>>", - JSON.stringify(response?.data?.Profile?.id, null, 2) + JSON.stringify(response?.data?.Profile?.id, null, 2), ); setData(response.data); @@ -89,7 +89,12 @@ export default function Application() { /> + } footerComponent={ + -File source: app/(application)/(user)/donation/(tabs)/status.tsx +File source: app/(application)/(user)/donation/[id]/fund-disbursement.tsx Folder tujuan: screens/Donation -Nama file utama: ScreenStatus.tsx +Nama file utama: ScreenFundDisbursement.tsx -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" +Buat file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah nama function menjadi "Donation_ScreenFundDisbursement" kemudian clean code, import dan panggil function tersebut pada file "File source" Selanjutnya terapkan pagination pada file "Nama file utama" -Function fecth: apiDonationGetByStatus +Function fecth: apiDonationDisbursementOfFundsListById File function fetch: service/api-client/api-donation.ts File komponen wrapper: components/_ShareComponent/NewWrapper.tsx @@ -22,15 +22,15 @@ Jika tidak ada props page maka tambahkan props page dan default page: "1" Gunakan bahasa indonesia pada cli agar saya mudah membacanya. -File refrensi: screens/Event/ScreenStatus.tsx +File refrensi: screens/Donation/ScreenListOfNews.tsx Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama - + - -File utama: screens/Invesment/ScreenTransaction.tsx -Function fecth: apiInvestmentGetInvoice -File function fetch: service/api-client/api-investment.ts + +File utama: screens/Donation/ScreenFundDisbursement.tsx +Function fecth: apiDonationDisbursementOfFundsListById +File function fetch: service/api-client/api-donation.ts File komponen wrapper: components/_ShareComponent/NewWrapper.tsx Terapkan pagination pada file "File utama" @@ -43,7 +43,28 @@ Jika tidak ada props page maka tambahkan props page dan default page: "1" Gunakan bahasa indonesia pada cli agar saya mudah membacanya. - + + +Masukan kode berikut di prop ListHeaderComponent: + + + + + + Rp. {formatCurrencyDisplay(data?.totalPencairan)} + + Total Pencairan Dana + + + + {data?.akumulasiPencairan} kali + + Akumulasi Pencairan + + + + + Terapkan NewWrapper pada file: app/(application)/(user)/donation/create.tsx @@ -57,3 +78,12 @@ jika tidak maka terapkan sesuai dengan logika yang sudah ada Bagaimana menangani bug berikut pada file berikut: screens/Invesment/Document/ScreenRecap.tsx Ini adalah halaman yang memiliki fungsi pagination , saya membuat data dummy dimana menghasilkan data urut 1-9, saya mencoba memuat halaman setiap page nya 4 saja untuk percobaan. Saat awal muncul komponent box dengan data 9 - 6, kemudian saya hapus data ke 8 . lalu saya coba scroll ke bawah seharusnya angka akan tetap urut 9, 7, 6, 5, 4 ... 1. Tapi dalam case ini setelah 8 di hapus kemudian saya scroll box ke 5 tidak muncul saat di scroll. Apakah anda mengerti maksud saya ? + + +Branch: loaddata/10-feb-26 +Jalankan perintah ini: git checkout -b "Branch" +Setelah itu jalankan perintah ini: git add . +Setelah itu jalankan perintah ini: git commit -m " + +" +Setelah itu jalankan perintah ini: git push origin "Branch" \ No newline at end of file diff --git a/screens/Donation/BoxNews.tsx b/screens/Donation/BoxNews.tsx new file mode 100644 index 0000000..14376e7 --- /dev/null +++ b/screens/Donation/BoxNews.tsx @@ -0,0 +1,21 @@ +import { BaseBox, Grid, Spacing, TextCustom } from "@/components"; +import { formatChatTime } from "@/utils/formatChatTime"; + +export default function Donation_BoxNews({item}: {item: any}){ + return <> + + + + + {item?.title || "-"} + + + + + {formatChatTime(item?.createdAt)} + + + + + +} \ No newline at end of file diff --git a/screens/Donation/ScreenBeranda.tsx b/screens/Donation/ScreenBeranda.tsx new file mode 100644 index 0000000..95b8842 --- /dev/null +++ b/screens/Donation/ScreenBeranda.tsx @@ -0,0 +1,62 @@ +import FloatingButton from "@/components/Button/FloatingButton"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { usePagination } from "@/hooks/use-pagination"; +import Donation_BoxPublish from "@/screens/Donation/BoxPublish"; +import { apiDonationGetAll } from "@/service/api-client/api-donation"; +import { router } from "expo-router"; +import { RefreshControl } from "react-native"; + +export default function Donation_ScreenBeranda() { + const pagination = usePagination({ + fetchFunction: async (page, search) => { + return await apiDonationGetAll({ + category: "beranda", + page: String(page), + }).then((res) => { + console.log("RES", JSON.stringify(res, null, 2)); + return res; + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman yang diinginkan + onError: (error) => console.error("[ERROR] Fetch event beranda:", error), + dependencies: [], + }); + + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: "Belum ada donasi", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 150, + }); + + return ( + ( + + )} + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + refreshControl={ + + } + hideFooter + floatingButton={ + router.push("/donation/create")} /> + } + /> + ); +} diff --git a/screens/Donation/ScreenFundDisbursement.tsx b/screens/Donation/ScreenFundDisbursement.tsx new file mode 100644 index 0000000..3a3d358 --- /dev/null +++ b/screens/Donation/ScreenFundDisbursement.tsx @@ -0,0 +1,174 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + ActionIcon, + BaseBox, + Grid, + InformationBox, + Spacing, + StackCustom, + TextCustom, +} from "@/components"; +import { MainColor } from "@/constants/color-palet"; +import { usePagination } from "@/hooks/use-pagination"; +import { + apiDonationDisbursementOfFundsListById, + apiDonationGetOne, +} from "@/service/api-client/api-donation"; +import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; +import { Feather } from "@expo/vector-icons"; +import dayjs from "dayjs"; +import { router, useLocalSearchParams } from "expo-router"; +import _ from "lodash"; +import React, { useState } from "react"; +import { RefreshControl, View } from "react-native"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { Divider } from "react-native-paper"; + +interface Donation_ScreenFundDisbursementProps { + donationId: string; +} + +export default function Donation_ScreenFundDisbursement({ + donationId, +}: Donation_ScreenFundDisbursementProps) { + const [data, setData] = useState({ + totalPencairan: 0, + akumulasiPencairan: 0, + }); + + // Ambil data utama (total pencairan, dll) terpisah dari pagination + React.useEffect(() => { + const onLoadData = async () => { + try { + const responseData = await apiDonationGetOne({ + id: donationId, + category: "permanent", + }); + + if (responseData.success) { + setData({ + totalPencairan: responseData.data.totalPencairan, + akumulasiPencairan: responseData.data.akumulasiPencairan, + }); + } + } catch (error) { + console.log("[ERROR]", error); + } + }; + + onLoadData(); + }, [donationId]); + + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiDonationDisbursementOfFundsListById({ + id: donationId, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API + dependencies: [donationId], + }); + + const renderItem = ({ item, index }: { item: any; index: number }) => ( + + + + + {item?.title} + + + + {dayjs(item?.createdAt).format("DD MMM YYYY")} + + + + {item?.deskripsi} + {/* */} + + + + + Rp. {formatCurrencyDisplay(item?.nominalCair)} + + + + } + onPress={() => { + router.navigate( + `/(application)/(image)/preview-image/${item?.imageId}`, + ); + }} + /> + + + {/* + { + router.navigate( + `/(application)/(image)/preview-image/${item?.imageId}`, + ); + }} + icon="file-text" + > + Bukti Transaksi + */} + + + ); + + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: "Belum ada data", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 150, + }); + + // Komponen header yang akan ditampilkan di atas daftar + const ListHeaderComponent = ( + + + + + + + Rp. {formatCurrencyDisplay(data?.totalPencairan)} + + Total Pencairan Dana + + + + {data?.akumulasiPencairan} kali + + Akumulasi Pencairan + + + + + ); + + return ( + + } + hideFooter + /> + ); +} diff --git a/screens/Donation/ScreenListOfDonatur.tsx b/screens/Donation/ScreenListOfDonatur.tsx new file mode 100644 index 0000000..66aa3f4 --- /dev/null +++ b/screens/Donation/ScreenListOfDonatur.tsx @@ -0,0 +1,98 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + BaseBox, + Grid, + LoaderCustom, + Spacing, + StackCustom, + TextCustom, +} from "@/components"; +import { MainColor } from "@/constants/color-palet"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiDonationListOfDonaturById } from "@/service/api-client/api-donation"; +import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; +import { FontAwesome6 } from "@expo/vector-icons"; +import dayjs from "dayjs"; +import { RefreshControl } from "react-native"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; + +interface Donation_ScreenListOfDonaturProps { + donationId: string; +} + +export default function Donation_ScreenListOfDonatur({ + donationId, +}: Donation_ScreenListOfDonaturProps) { + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiDonationListOfDonaturById({ + id: donationId, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API + dependencies: [donationId], + }); + + const renderItem = ({ item, index }: { item: any; index: number }) => ( + + + + + + + + {item?.Author?.username || "-"} + + + + Berdonas sebesar + + Rp. {formatCurrencyDisplay(item?.nominal)} + + + {dayjs(item?.createdAt).format("DD MMM YYYY, HH:mm")} + + + + + + ); + + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: "Belum ada donatur", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 120, + }); + + return ( + + } + /> + ); +} diff --git a/screens/Donation/ScreenListOfNews.tsx b/screens/Donation/ScreenListOfNews.tsx new file mode 100644 index 0000000..baeaf40 --- /dev/null +++ b/screens/Donation/ScreenListOfNews.tsx @@ -0,0 +1,97 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { BackButton, DrawerCustom, MenuDrawerDynamicGrid } from "@/components"; +import { IconPlus } from "@/components/_Icon"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiDonationGetNewsById } from "@/service/api-client/api-donation"; +import { router, Stack } from "expo-router"; +import { useState } from "react"; +import { RefreshControl } from "react-native"; +import Donation_BoxNews from "./BoxNews"; + +interface Donation_ScreenListOfNewsProps { + donationId: string; +} + +export default function Donation_ScreenListOfNews({ + donationId, +}: Donation_ScreenListOfNewsProps) { + const [openDrawer, setOpenDrawer] = useState(false); + + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiDonationGetNewsById({ + id: donationId, + category: "get-all", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API + dependencies: [donationId], + }); + + 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 kabar", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 80, + }); + + return ( + <> + , + }} + /> + + } + /> + + setOpenDrawer(false)} + height={"auto"} + > + , + label: "Tambah Berita", + path: `/donation/${donationId}/(news)/add-news`, + }, + ]} + onPressItem={(item) => { + console.log("PATH ", item.path); + router.navigate(item.path as any); + setOpenDrawer(false); + }} + /> + + + ); +} diff --git a/screens/Donation/ScreenMyDonation.tsx b/screens/Donation/ScreenMyDonation.tsx new file mode 100644 index 0000000..1bd0488 --- /dev/null +++ b/screens/Donation/ScreenMyDonation.tsx @@ -0,0 +1,152 @@ +import { + BadgeCustom, + BaseBox, + DummyLandscapeImage, + Grid, + StackCustom, + TextCustom, +} from "@/components"; +import FloatingButton from "@/components/Button/FloatingButton"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { useAuth } from "@/hooks/use-auth"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiDonationGetAll } from "@/service/api-client/api-donation"; +import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; +import { Href, router } from "expo-router"; +import _ from "lodash"; +import { RefreshControl, View } from "react-native"; + +export default function Donation_ScreenMyDonation() { + const { user } = useAuth(); + + const pagination = usePagination({ + fetchFunction: async (page, search) => { + if (!user?.id) { + throw new Error("User tidak ditemukan"); + } + + return await apiDonationGetAll({ + category: "my-donation", + authorId: user?.id, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman yang diinginkan + dependencies: [user?.id], // Reload ketika user.id berubah + }); + + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: "Belum ada transaksi", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 150, + }); + + const handlerColor = (status: string) => { + if (status === "menunggu") { + return "orange"; + } else if (status === "proses") { + return "white"; + } else if (status === "berhasil") { + return "green"; + } else if (status === "gagal") { + return "red"; + } + }; + + const handlePress = ({ + invoiceId, + donationId, + status, + }: { + invoiceId: string; + donationId: string; + status: string; + }) => { + const url: Href = `../${donationId}/(transaction-flow)/${invoiceId}`; + if (status === "menunggu") { + router.push(`${url}/invoice`); + } else if (status === "proses") { + router.push(`${url}/process`); + } else if (status === "berhasil") { + router.push(`${url}/success`); + } else if (status === "gagal") { + router.push(`${url}/failed`); + } + }; + + const renderItem = ({ item }: { item: any }) => ( + { + handlePress({ + status: _.lowerCase(item.statusInvoice), + invoiceId: item.id, + donationId: item.donasiId, + }); + }} + > + + + + + + + + + + + {item.title || "-"} + + + + Rp. {formatCurrencyDisplay(item.nominal)} + + + + {item.statusInvoice} + + + + + + ); + + return ( + + } + hideFooter + floatingButton={ + router.push("/donation/create")} /> + } + /> + ); +} diff --git a/screens/Donation/ScreenRecapOfNews.tsx b/screens/Donation/ScreenRecapOfNews.tsx new file mode 100644 index 0000000..adebc5d --- /dev/null +++ b/screens/Donation/ScreenRecapOfNews.tsx @@ -0,0 +1,105 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + BackButton, + DotButton, + DrawerCustom, + MenuDrawerDynamicGrid, +} from "@/components"; +import { IconPlus } from "@/components/_Icon"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiDonationGetNewsById } from "@/service/api-client/api-donation"; +import { router, Stack, useFocusEffect } from "expo-router"; +import { useCallback, useState } from "react"; +import { RefreshControl } from "react-native"; +import Donation_BoxNews from "./BoxNews"; + +interface Donation_ScreenRecapOfNewsProps { + donationId: string; +} + +export default function Donation_ScreenRecapOfNews({ + donationId, +}: Donation_ScreenRecapOfNewsProps) { + const [openDrawer, setOpenDrawer] = useState(false); + + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiDonationGetNewsById({ + id: donationId, + category: "get-all", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API + dependencies: [donationId], + }); + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [donationId]), + ); + + 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 kabar", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 80, + }); + + return ( + <> + , + headerRight: () => setOpenDrawer(true)} />, + }} + /> + + } + /> + + setOpenDrawer(false)} + height={"auto"} + > + , + label: "Tambah Berita", + path: `/donation/${donationId}/(news)/add-news`, + }, + ]} + onPressItem={(item) => { + console.log("PATH ", item.path); + router.navigate(item.path as any); + setOpenDrawer(false); + }} + /> + + + ); +} diff --git a/screens/Donation/ScreenStatus.tsx b/screens/Donation/ScreenStatus.tsx index ea4df82..7ef3943 100644 --- a/screens/Donation/ScreenStatus.tsx +++ b/screens/Donation/ScreenStatus.tsx @@ -1,28 +1,29 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - LoaderCustom, - ScrollableCustom, - TextCustom, -} from "@/components"; +import { ScrollableCustom } from "@/components"; import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; import { useAuth } from "@/hooks/use-auth"; +import { usePagination } from "@/hooks/use-pagination"; 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) { +export default function Donation_ScreenStatus({ + initialStatus = "publish", +}: DonationStatusProps) { const { user } = useAuth(); - const [activeCategory, setActiveCategory] = useState(initialStatus); + const [activeCategory, setActiveCategory] = useState( + initialStatus, + ); const pagination = usePagination({ fetchFunction: async (page) => { @@ -32,18 +33,19 @@ export default function Donation_ScreenStatus({ initialStatus = "publish" }: Don page: String(page), }); }, - pageSize: 5, // Sesuaikan dengan jumlah item per halaman dari API + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API dependencies: [user?.id, activeCategory], }); useFocusEffect( useCallback(() => { pagination.onRefresh(); - }, [user?.id, activeCategory]) + }, [user?.id, activeCategory]), ); const handlePress = (item: any) => { setActiveCategory(item.value); + pagination.reset(); }; const scrollComponent = ( @@ -59,21 +61,19 @@ export default function Donation_ScreenStatus({ initialStatus = "publish" }: Don ); 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, - }); + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: `Tidak ada data ${activeCategory}`, + skeletonCount: 5, + skeletonHeight: 120, + }); return ( } hideFooter headerComponent={scrollComponent} /> ); -} \ No newline at end of file +} diff --git a/service/api-admin/api-admin-donation.ts b/service/api-admin/api-admin-donation.ts index 18490b1..a0aa925 100644 --- a/service/api-admin/api-admin-donation.ts +++ b/service/api-admin/api-admin-donation.ts @@ -52,15 +52,18 @@ export async function apiAdminDonationUpdateStatus({ export async function apiAdminDonationListOfDonatur({ id, status, + page = "1" }: { id: string; status: "berhasil" | "gagal" | "proses" | "menunggu" | null; + page?: string; }) { - const query = status && status !== null ? `?status=${status}` : ""; + const statusQuery = status && status !== null ? `&status=${status}` : ""; + const pageQuery = `&page=${page}`; try { const response = await apiConfig.get( - `/mobile/admin/donation/${id}/donatur${query}` + `/mobile/admin/donation/${id}/donatur?${statusQuery}${pageQuery}` ); return response.data; } catch (error) { @@ -105,19 +108,6 @@ export async function apiAdminDonationInvoiceUpdateById({ } } -export async function apiAdminDonationListOfDonaturById({ - id, -}: { - id: string; -}) { - try { - const response = await apiConfig.get(`/mobile/donation/${id}/donatur`); - return response.data; - } catch (error) { - throw error; - } -} - export async function apiAdminDonationDisbursementOfFundsCreated({ id, data, diff --git a/service/api-client/api-donation.ts b/service/api-client/api-donation.ts index d632525..b1a96dc 100644 --- a/service/api-client/api-donation.ts +++ b/service/api-client/api-donation.ts @@ -109,14 +109,17 @@ export async function apiDonationUpdateData({ export async function apiDonationGetAll({ category, authorId, + page = "1" }: { category: "beranda" | "my-donation"; authorId?: string; + page?: string; }) { const authorQuery = authorId ? `&authorId=${authorId}` : ""; + const pageQuery = `&page=${page}`; try { const response = await apiConfig.get( - `/mobile/donation?category=${category}${authorQuery}` + `/mobile/donation?category=${category}${authorQuery}${pageQuery}` ); return response.data; } catch (error) { @@ -219,13 +222,15 @@ export async function apiDonationCreateNews({ export async function apiDonationGetNewsById({ id, category, + page = "1" }: { id: string; category: "get-all" | "get-one"; + page?: string; }) { try { const response = await apiConfig.get( - `/mobile/donation/${id}/news?category=${category}` + `/mobile/donation/${id}/news?category=${category}&page=${page}` ); return response.data; } catch (error) { @@ -261,11 +266,28 @@ export async function apiDonationDeleteNews({ id }: { id: string }) { export async function apiDonationDisbursementOfFundsListById({ id, + page = "1" }: { id: string; + page?: string; }) { try { - const response = await apiConfig.get(`/mobile/donation/${id}/disbursement`); + const response = await apiConfig.get(`/mobile/donation/${id}/disbursement?page=${page}`); + return response.data; + } catch (error) { + throw error; + } +} + +export async function apiDonationListOfDonaturById({ + id, + page = "1" +}: { + id: string; + page?: string; +}) { + try { + const response = await apiConfig.get(`/mobile/donation/${id}/donatur?page=${page}`); return response.data; } catch (error) { throw error; diff --git a/utils/formatChatTime.ts b/utils/formatChatTime.ts index 9df79ed..599a120 100644 --- a/utils/formatChatTime.ts +++ b/utils/formatChatTime.ts @@ -31,5 +31,5 @@ export const formatChatTime = (date: string | Date): string => { } // Lebih dari 7 hari lalu - return messageDate.format('DD - MM - YYYY, HH.mm'); // "05 - 11 - 2025, 14.00" + return messageDate.format('DD/MM/YYYY, HH.mm'); // "05/11/2025, 14.00" }; \ No newline at end of file