From 83fa277e0357690f5f9a6ac13b3c341be46b2fb2 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Fri, 6 Feb 2026 17:27:12 +0800 Subject: [PATCH] Fix Loaddata Invesment & Clearing code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI – Investment (User) - app/(application)/(user)/investment/(tabs)/index.tsx - app/(application)/(user)/investment/(tabs)/portofolio.tsx - app/(application)/(user)/investment/(tabs)/transaction.tsx - app/(application)/(user)/investment/[id]/(document)/list-of-document.tsx - app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx - app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx - app/(application)/(user)/investment/[id]/(transaction-flow)/select-bank.tsx Screens – Investment - screens/Invesment/ButtonStatusSection.tsx - screens/Invesment/Document/RecapBoxDetail.tsx - screens/Invesment/Document/ScreenListDocument.tsx - screens/Invesment/Document/ScreenRecap.tsx - screens/Invesment/ScreenBursa.tsx - screens/Invesment/ScreenPortofolio.tsx - screens/Invesment/ScreenTransaction.tsx Profile - app/(application)/(user)/profile/[id]/detail-blocked.tsx API Client - service/api-client/api-investment.ts Docs - docs/prompt-for-qwen-code.md ### No issue --- .../(user)/investment/(tabs)/index.tsx | 55 +---- .../(user)/investment/(tabs)/portofolio.tsx | 80 +----- .../(user)/investment/(tabs)/transaction.tsx | 122 +-------- .../[id]/(document)/list-of-document.tsx | 56 +---- .../[id]/(document)/recap-of-document.tsx | 207 +--------------- .../[id]/(transaction-flow)/invoice.tsx | 224 +---------------- .../[id]/(transaction-flow)/select-bank.tsx | 1 - .../(user)/profile/[id]/detail-blocked.tsx | 1 - docs/prompt-for-qwen-code.md | 12 +- screens/Invesment/ButtonStatusSection.tsx | 17 +- screens/Invesment/Document/RecapBoxDetail.tsx | 5 +- .../Invesment/Document/ScreenListDocument.tsx | 76 ++++++ screens/Invesment/Document/ScreenRecap.tsx | 233 ++++++++++++++++++ screens/Invesment/ScreenBursa.tsx | 67 +++++ screens/Invesment/ScreenPortofolio.tsx | 102 ++++++++ screens/Invesment/ScreenTransaction.tsx | 144 +++++++++++ service/api-client/api-investment.ts | 17 +- 17 files changed, 673 insertions(+), 746 deletions(-) create mode 100644 screens/Invesment/Document/ScreenListDocument.tsx create mode 100644 screens/Invesment/Document/ScreenRecap.tsx create mode 100644 screens/Invesment/ScreenBursa.tsx create mode 100644 screens/Invesment/ScreenPortofolio.tsx create mode 100644 screens/Invesment/ScreenTransaction.tsx diff --git a/app/(application)/(user)/investment/(tabs)/index.tsx b/app/(application)/(user)/investment/(tabs)/index.tsx index 328fae0..1a8ea93 100644 --- a/app/(application)/(user)/investment/(tabs)/index.tsx +++ b/app/(application)/(user)/investment/(tabs)/index.tsx @@ -1,56 +1,9 @@ -import { - FloatingButton, - LoaderCustom, - ViewWrapper -} from "@/components"; -import NoDataText from "@/components/_ShareComponent/NoDataText"; -import Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection"; -import { apiInvestmentGetAll } from "@/service/api-client/api-investment"; -import { router, useFocusEffect } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Investment_ScreenBursa from "@/screens/Invesment/ScreenBursa"; export default function InvestmentBursa() { - const [list, setList] = useState(null); - const [loadingList, setLoadingList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadList(); - }, []) - ); - - const onLoadList = async () => { - try { - setLoadingList(true); - const response = await apiInvestmentGetAll({ - category: "bursa" - }); - // console.log("[DATA LIST]", JSON.stringify(response.data, null, 2)); - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadingList(false); - } - }; - return ( - router.push("/investment/create")} /> - } - > - {loadingList ? ( - - ) : _.isEmpty(list) ? ( - - ) : ( - list?.map((item: any, index: number) => ( - - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/investment/(tabs)/portofolio.tsx b/app/(application)/(user)/investment/(tabs)/portofolio.tsx index 46c85aa..feec9f2 100644 --- a/app/(application)/(user)/investment/(tabs)/portofolio.tsx +++ b/app/(application)/(user)/investment/(tabs)/portofolio.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 Investment_StatusBox from "@/screens/Invesment/StatusBox"; -import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment"; -import { useFocusEffect, useLocalSearchParams } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Investment_ScreenPortofolio from "@/screens/Invesment/ScreenPortofolio"; export default function InvestmentPortofolio() { - const { user } = useAuth(); - const { status } = useLocalSearchParams<{ status?: string }>(); - - const [activeCategory, setActiveCategory] = useState( - status || "publish" - ); - - const [listData, setListData] = useState([]); - const [loadingList, setLoadingList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, [user?.id, activeCategory]) - ); - - const onLoadData = async () => { - try { - setLoadingList(true); - const response = await apiInvestmentGetByStatus({ - authorId: user?.id as string, - status: activeCategory as string, - }); - setListData(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadingList(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 ( - - {loadingList ? ( - - ) : _.isEmpty(listData) ? ( - Tidak ada data {activeCategory} - ) : ( - listData.map((item: any, index: number) => ( - - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/investment/(tabs)/transaction.tsx b/app/(application)/(user)/investment/(tabs)/transaction.tsx index 99b24be..4dae7da 100644 --- a/app/(application)/(user)/investment/(tabs)/transaction.tsx +++ b/app/(application)/(user)/investment/(tabs)/transaction.tsx @@ -1,124 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - BadgeCustom, - BaseBox, - Grid, - LoaderCustom, - StackCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import NoDataText from "@/components/_ShareComponent/NoDataText"; -import { useAuth } from "@/hooks/use-auth"; -import { apiInvestmentGetInvoice } from "@/service/api-client/api-investment"; -import { GStyles } from "@/styles/global-styles"; -import { formatChatTime } from "@/utils/formatChatTime"; -import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; -import { router, useFocusEffect } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; -import { View } from "react-native"; +import Investment_ScreenTransaction from "@/screens/Invesment/ScreenTransaction"; export default function InvestmentTransaction() { - const { user } = useAuth(); - const [list, setList] = useState([]); - const [loadList, setLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadList(); - }, [user?.id]) - ); - - const onLoadList = async () => { - try { - setLoadList(true); - const response = await apiInvestmentGetInvoice({ - authorId: user?.id as string, - category: "transaction", - }); - console.log("[RESPONSE LIST]", JSON.stringify(response.data, null, 2)); - 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 = ({ id, status }: { id: string; status: string }) => { - if (status === "menunggu") { - router.push(`/investment/${id}/(transaction-flow)/invoice`); - } else if (status === "proses") { - router.push(`/investment/${id}/(transaction-flow)/process`); - } else if (status === "berhasil") { - router.push(`/investment/${id}/(transaction-flow)/success`); - } else if (status === "gagal") { - router.push(`/investment/${id}/(transaction-flow)/failed`); - } - }; - return ( - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - ) : ( - list.map((item: any, i: number) => ( - { - handlePress({ - id: item.id, - status: _.lowerCase(item.statusInvoice), - }); - }} - > - - - - {item?.title || "-"} - - {formatChatTime(item?.createdAt)} - - - - - - - - - - Rp. {formatCurrencyDisplay(item?.nominal) || "-"} - - - {item?.statusInvoice || "-"} - - - - - - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/investment/[id]/(document)/list-of-document.tsx b/app/(application)/(user)/investment/[id]/(document)/list-of-document.tsx index ed76962..eb5fedd 100644 --- a/app/(application)/(user)/investment/[id]/(document)/list-of-document.tsx +++ b/app/(application)/(user)/investment/[id]/(document)/list-of-document.tsx @@ -1,58 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { LoaderCustom, TextCustom, ViewWrapper } from "@/components"; -import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; -import { apiInvestmentGetDocument } from "@/service/api-client/api-investment"; -import { useFocusEffect, useLocalSearchParams } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Investment_ScreenListOfDocument from "@/screens/Invesment/Document/ScreenListDocument"; export default function InvestmentListOfDocument() { - const { id } = useLocalSearchParams(); - console.log("ID >> ", id); - - const [list, setList] = useState(null); - const [loadList, setLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadListDocument(); - }, [id]) - ); - - const onLoadListDocument = async () => { - try { - setLoadList(true); - const response = await apiInvestmentGetDocument({ - id: id as string, - category: "all-document", - }); - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - setList([]); - } finally { - setLoadList(false); - } - }; - return ( - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Tidak ada data - - ) : ( - list?.map((item: any, index: number) => ( - - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx b/app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx index 840aee7..5000a92 100644 --- a/app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx +++ b/app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx @@ -1,213 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - AlertDefaultSystem, - BackButton, - DotButton, - DrawerCustom, - LoaderCustom, - MenuDrawerDynamicGrid, - TextCustom, - ViewWrapper, -} from "@/components"; -import { IconEdit } from "@/components/_Icon"; -import { MainColor } from "@/constants/color-palet"; -import { ICON_SIZE_SMALL } from "@/constants/constans-value"; -import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; -import { - apiInvestmentDeleteDocument, - apiInvestmentGetDocument, -} from "@/service/api-client/api-investment"; -import { AntDesign, Ionicons } from "@expo/vector-icons"; -import { - router, - Stack, - useFocusEffect, - useLocalSearchParams, -} from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; -import Toast from "react-native-toast-message"; +import Investment_ScreenRecap from "@/screens/Invesment/Document/ScreenRecap"; export default function InvestmentRecapOfDocument() { - const { id } = useLocalSearchParams(); - const [openDrawer, setOpenDrawer] = useState(false); - const [openDrawerBox, setOpenDrawerBox] = useState(false); - const [list, setList] = useState(null); - const [loadList, setLoadList] = useState(false); - const [selectId, setSelectId] = useState(null); - - useFocusEffect( - useCallback(() => { - onLoadListDocument(); - }, [id]) - ); - - const onLoadListDocument = async () => { - try { - setLoadList(true); - const response = await apiInvestmentGetDocument({ - id: id as string, - category: "all-document", - }); - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - setList([]); - } finally { - setLoadList(false); - } - }; - - const handlerDeleteDocument = async () => { - try { - const response = await apiInvestmentDeleteDocument({ - id: selectId as string, - }); - - if (response.success) { - Toast.show({ - type: "success", - text1: "Data berhasil dihapus", - }); - setList((prev: any[] | null) => { - if (!prev) return null; - return prev.filter((item: any) => item.id !== selectId); - }); - setOpenDrawerBox(false); - setSelectId(null); - } - } catch (error) { - console.log("[ERROR]", error); - Toast.show({ - type: "error", - text1: "Gagal menghapus data", - }); - } - }; - return ( <> - , - headerRight: () => ( - { - setOpenDrawer(true); - setOpenDrawerBox(false); - }} - /> - ), - }} - /> - - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Tidak ada data - - ) : ( - list?.map((item: any, index: number) => ( - { - setSelectId(item.id); - setOpenDrawerBox(true); - }} - /> - } - href={`/(file)/${item.fileId}`} - /> - )) - )} - - - {/* Drawer On Header */} - setOpenDrawer(false)} - height={"auto"} - > - - ), - label: "Tambah Dokumen", - path: `/investment/${id}/(document)/add-document`, - }, - ]} - onPressItem={(item) => { - router.push(item.path as any); - setOpenDrawer(false); - }} - /> - - - {/* Drawer On Box */} - setOpenDrawerBox(false)} - height={"auto"} - > - , - label: "Edit Dokumen", - path: `/investment/${selectId}/(document)/edit-document`, - }, - { - icon: ( - - ), - label: "Hapus Dokumen", - path: "" as any, - color: MainColor.red, - }, - ]} - onPressItem={(item) => { - if (item.path === ("" as any)) { - AlertDefaultSystem({ - title: "Hapus Dokumen", - message: "Apakah anda yakin ingin menghapus dokumen ini?", - textLeft: "Batal", - textRight: "Hapus", - onPressRight: () => { - handlerDeleteDocument(); - }, - }); - } else { - router.push(item.path as any); - } - - setOpenDrawerBox(false); - }} - /> - + ); } diff --git a/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx b/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx index fc9c00d..ecdd0bc 100644 --- a/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx +++ b/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx @@ -1,230 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - BaseBox, - ButtonCenteredOnly, - ButtonCustom, - Grid, - InformationBox, - Spacing, - StackCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import CopyButton from "@/components/Button/CoyButton"; -import { MainColor } from "@/constants/color-palet"; -import DIRECTORY_ID from "@/constants/directory-id"; -import { - apiInvestmentGetInvoice, - apiInvestmentUpdateInvoice, -} from "@/service/api-client/api-investment"; -import { uploadFileService } from "@/service/upload-service"; -import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; -import pickFile, { IFileData } from "@/utils/pickFile"; -import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; -import { useCallback, useState } from "react"; -import { View } from "react-native"; -import Toast from "react-native-toast-message"; +import Investment_ScreenTransaction from "@/screens/Invesment/ScreenTransaction"; export default function InvestmentInvoice() { - const { id } = useLocalSearchParams(); - const [data, setData] = useState({}); - const [image, setImage] = useState({ - name: "", - uri: "", - size: 0, - }); - const [isLoading, setIsLoading] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, [id]) - ); - - const onLoadData = async () => { - try { - const response = await apiInvestmentGetInvoice({ - id: id as string, - category: "invoice", - }); - - setData(response.data); - } catch (error) { - console.log("[ERROR]", error); - } - }; - - const handlerSubmitUpdate = async () => { - try { - setIsLoading(true); - const responseUploadImage = await uploadFileService({ - dirId: DIRECTORY_ID.investasi_bukti_transfer, - imageUri: image?.uri, - }); - - if (!responseUploadImage?.data?.id) { - Toast.show({ - type: "error", - text1: "Gagal mengunggah bukti transfer", - }); - return; - } - - const response = await apiInvestmentUpdateInvoice({ - id: id as string, - data: { - imageId: responseUploadImage?.data?.id, - }, - status: "proses", - }); - - if (response.success) { - Toast.show({ - type: "success", - text1: "Berhasil mengunggah bukti transfer", - }); - router.push(`/investment/${id}/(transaction-flow)/process`); - } - } catch (error) { - console.log("[ERROR]", error); - } finally { - setIsLoading(false); - } - }; - return ( <> - - - - - - - - Bank - - - {data?.MasterBank?.namaBank} - - - - - - Nama Akun - - - {data?.MasterBank?.namaAkun} - - - - - - - - {data?.MasterBank?.norek} - - - - - - - - - - - - - Jumlah Transaksi - - - - - - - - Rp. {formatCurrencyDisplay(data?.nominal)} - - - - - - - - - - - - - - Upload bukti transfer anda. - - {image ? ( - - - {image?.name} - - - ) : ( - - Tidak ada gambar yang diunggah - - )} - { - pickFile({ - allowedType: "image", - setImageUri(file: any) { - setImage(file); - }, - }); - }} - icon="upload" - > - Upload - - - - - { - handlerSubmitUpdate(); - }} - > - Saya Sudah Transfer - - - - + ); } diff --git a/app/(application)/(user)/investment/[id]/(transaction-flow)/select-bank.tsx b/app/(application)/(user)/investment/[id]/(transaction-flow)/select-bank.tsx index 9deb392..311385f 100644 --- a/app/(application)/(user)/investment/[id]/(transaction-flow)/select-bank.tsx +++ b/app/(application)/(user)/investment/[id]/(transaction-flow)/select-bank.tsx @@ -56,7 +56,6 @@ export default function InvestmentSelectBank() { }); if (response.success) { - console.log("[RESPONSE >>]", response); const invoiceId = response.data.id; const delStorage = await AsyncStorage.removeItem( diff --git a/app/(application)/(user)/profile/[id]/detail-blocked.tsx b/app/(application)/(user)/profile/[id]/detail-blocked.tsx index 920cb25..45a663b 100644 --- a/app/(application)/(user)/profile/[id]/detail-blocked.tsx +++ b/app/(application)/(user)/profile/[id]/detail-blocked.tsx @@ -29,7 +29,6 @@ export default function ProfileDetailBlocked() { const fetchData = async () => { const response = await apiGetBlockedById({ id: String(id) }); - // console.log("[RESPONSE >>]", JSON.stringify(response, null, 2)); setData(response.data); }; diff --git a/docs/prompt-for-qwen-code.md b/docs/prompt-for-qwen-code.md index 952b35a..e0eff8f 100644 --- a/docs/prompt-for-qwen-code.md +++ b/docs/prompt-for-qwen-code.md @@ -1,8 +1,8 @@ -File utama: screens/Voting/ScreenListOfContributor.tsx -Function fecth: apiVotingContribution -File function fetch: service/api-client/api-voting.ts +File utama: screens/Invesment/ScreenTransaction.tsx +Function fecth: apiInvestmentGetInvoice +File function fetch: service/api-client/api-investment.ts File komponen wrapper: components/_ShareComponent/NewWrapper.tsx Terapkan pagination pada file "File utama" @@ -16,7 +16,7 @@ Jika tidak ada props page maka tambahkan props page dan default page: "1" Gunakan bahasa indonesia pada cli agar saya mudah membacanya. -File refrensi: app/(application)/(user)/event/[id]/list-of-participants.tsx +File refrensi: screens/Event/ScreenStatus.tsx Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama @@ -29,3 +29,7 @@ Component yang digunakan: components/_ShareComponent/NewWrapper.tsx , karena ini 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" 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 ? diff --git a/screens/Invesment/ButtonStatusSection.tsx b/screens/Invesment/ButtonStatusSection.tsx index a2231a4..5a98e47 100644 --- a/screens/Invesment/ButtonStatusSection.tsx +++ b/screens/Invesment/ButtonStatusSection.tsx @@ -16,6 +16,9 @@ export default function Investment_ButtonStatusSection({ status: string; buttonPublish?: React.ReactNode; }) { + const path : any= (status: string) => { + return `/investment/(tabs)/portofolio?status=${status}`; + }; const [isLoading, setIsLoading] = useState(false); const handleBatalkanReview = () => { AlertDefaultSystem({ @@ -30,13 +33,13 @@ export default function Investment_ButtonStatusSection({ id: id as string, status: "draft", }); - console.log("[RESPONSE]", JSON.stringify(response, null, 2)); + if (response.success) { Toast.show({ type: "success", text1: "Berhasil Batalkan Review", }); - router.back(); + router.replace(path("draft")); } else { Toast.show({ type: "error", @@ -65,13 +68,13 @@ export default function Investment_ButtonStatusSection({ id: id as string, status: "review", }); - console.log("[RESPONSE]", JSON.stringify(response, null, 2)); + if (response.success) { Toast.show({ type: "success", text1: "Berhasil Ajukan Review", }); - router.back(); + router.replace(path("review")); } else { Toast.show({ type: "error", @@ -100,13 +103,13 @@ export default function Investment_ButtonStatusSection({ id: id as string, status: "draft", }); - console.log("[RESPONSE]", JSON.stringify(response, null, 2)); + if (response.success) { Toast.show({ type: "success", text1: "Berhasil Update Status", }); - router.back(); + router.replace(path("draft")); } else { Toast.show({ type: "error", @@ -135,8 +138,6 @@ export default function Investment_ButtonStatusSection({ id: id as string, }); - console.log("[RESPONSE DELETE]", JSON.stringify(response, null, 2)); - if (response.success) { Toast.show({ type: "success", diff --git a/screens/Invesment/Document/RecapBoxDetail.tsx b/screens/Invesment/Document/RecapBoxDetail.tsx index b3092bc..76c23ee 100644 --- a/screens/Invesment/Document/RecapBoxDetail.tsx +++ b/screens/Invesment/Document/RecapBoxDetail.tsx @@ -16,10 +16,7 @@ export default function Investment_BoxDetailDocument({ router.push(href as any)}> - - {title || - `Judul Dokumen: Lorem, ipsum dolor sit amet consectetur adipisicing elit.`} - + {title || "-"} {leftIcon && {leftIcon}} diff --git a/screens/Invesment/Document/ScreenListDocument.tsx b/screens/Invesment/Document/ScreenListDocument.tsx new file mode 100644 index 0000000..c65f261 --- /dev/null +++ b/screens/Invesment/Document/ScreenListDocument.tsx @@ -0,0 +1,76 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { TextCustom } from "@/components"; +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 Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; +import { apiInvestmentGetDocument } from "@/service/api-client/api-investment"; +import { useFocusEffect, useLocalSearchParams } from "expo-router"; +import _ from "lodash"; +import { useCallback } from "react"; +import { RefreshControl } from "react-native"; + +export default function Investment_ScreenListOfDocument() { + const { id } = useLocalSearchParams(); + console.log("ID >> ", id); + + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + if (!id) return { data: [] }; + + return await apiInvestmentGetDocument({ + id: id as string, + category: "all-document", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [id], + onError: (error) => console.error("[ERROR] Fetch document:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Tidak ada dokumen", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); + + // Render item dokumen + const renderDocumentItem = ({ item }: { item: any }) => ( + + ); + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [id]), + ); + + return ( + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + /> + ); +} diff --git a/screens/Invesment/Document/ScreenRecap.tsx b/screens/Invesment/Document/ScreenRecap.tsx new file mode 100644 index 0000000..3b762fd --- /dev/null +++ b/screens/Invesment/Document/ScreenRecap.tsx @@ -0,0 +1,233 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + AlertDefaultSystem, + BackButton, + DotButton, + DrawerCustom, + MenuDrawerDynamicGrid, + TextCustom, +} from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { IconEdit } from "@/components/_Icon"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { MainColor } from "@/constants/color-palet"; +import { ICON_SIZE_SMALL } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { usePagination } from "@/hooks/use-pagination"; +import Investment_BoxDetailDocument from "@/screens/Invesment/Document/RecapBoxDetail"; +import { + apiInvestmentDeleteDocument, + apiInvestmentGetDocument, +} from "@/service/api-client/api-investment"; +import { AntDesign, Ionicons } from "@expo/vector-icons"; +import { + router, + Stack, + useFocusEffect, + useLocalSearchParams, +} from "expo-router"; +import _ from "lodash"; +import { useCallback, useState } from "react"; +import { RefreshControl } from "react-native"; +import Toast from "react-native-toast-message"; + +export default function Investment_ScreenRecap() { + const { id } = useLocalSearchParams(); + const [openDrawer, setOpenDrawer] = useState(false); + const [openDrawerBox, setOpenDrawerBox] = useState(false); + const [selectId, setSelectId] = useState(null); + + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + if (!id) return { data: [] }; + + return await apiInvestmentGetDocument({ + id: id as string, + category: "all-document", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [id], + onError: (error) => console.error("[ERROR] Fetch document:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Tidak ada dokumen", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); + + const handlerDeleteDocument = async () => { + try { + const response = await apiInvestmentDeleteDocument({ + id: selectId as string, + }); + + if (response.success) { + Toast.show({ + type: "success", + text1: "Data berhasil dihapus", + }); + // Hapus item dari list + pagination.setListData((prev: any) => { + if (!prev) return null; + return prev.filter((item: any) => item.id !== selectId); + }); + + pagination.onRefresh(); + setOpenDrawerBox(false); + setSelectId(null); + } + } catch (error) { + console.log("[ERROR]", error); + Toast.show({ + type: "error", + text1: "Gagal menghapus data", + }); + } + }; + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, []), + ); + + // Render item dokumen + const renderDocumentItem = ({ item }: { item: any }) => ( + { + setSelectId(item.id); + setOpenDrawerBox(true); + }} + /> + } + href={`/(file)/${item.fileId}`} + /> + ); + + return ( + <> + , + headerRight: () => ( + { + setOpenDrawer(true); + setOpenDrawerBox(false); + }} + /> + ), + }} + /> + + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + /> + + {/* Drawer On Header */} + setOpenDrawer(false)} + height={"auto"} + > + + ), + label: "Tambah Dokumen", + path: `/investment/${id}/(document)/add-document`, + }, + ]} + onPressItem={(item) => { + router.push(item.path as any); + setOpenDrawer(false); + }} + /> + + + {/* Drawer On Box */} + setOpenDrawerBox(false)} + height={"auto"} + > + , + label: "Edit Dokumen", + path: `/investment/${selectId}/(document)/edit-document`, + }, + { + icon: ( + + ), + label: "Hapus Dokumen", + path: "" as any, + color: MainColor.red, + }, + ]} + onPressItem={(item) => { + if (item.path === ("" as any)) { + AlertDefaultSystem({ + title: "Hapus Dokumen", + message: "Apakah anda yakin ingin menghapus dokumen ini?", + textLeft: "Batal", + textRight: "Hapus", + onPressRight: () => { + handlerDeleteDocument(); + }, + }); + } else { + router.push(item.path as any); + } + + setOpenDrawerBox(false); + }} + /> + + + ); +} diff --git a/screens/Invesment/ScreenBursa.tsx b/screens/Invesment/ScreenBursa.tsx new file mode 100644 index 0000000..210fc3e --- /dev/null +++ b/screens/Invesment/ScreenBursa.tsx @@ -0,0 +1,67 @@ +import { + FloatingButton, + TextCustom, +} from "@/components"; +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 Investment_BoxBerandaSection from "@/screens/Invesment/BoxBerandaSection"; +import { apiInvestmentGetAll } from "@/service/api-client/api-investment"; +import { router } from "expo-router"; +import _ from "lodash"; +import { RefreshControl } from "react-native"; + +export default function Investment_ScreenBursa() { + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiInvestmentGetAll({ + category: "bursa", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [], + onError: (error) => console.error("[ERROR] Fetch investment bursa:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Belum ada investasi", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); + + // Render item investment + const renderInvestmentItem = ({ item }: { item: any }) => ( + + ); + + return ( + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + hideFooter + floatingButton={ + router.push("/investment/create")} /> + } + /> + ); +} diff --git a/screens/Invesment/ScreenPortofolio.tsx b/screens/Invesment/ScreenPortofolio.tsx new file mode 100644 index 0000000..08b7fe3 --- /dev/null +++ b/screens/Invesment/ScreenPortofolio.tsx @@ -0,0 +1,102 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { ScrollableCustom, TextCustom } from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +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 Investment_StatusBox from "@/screens/Invesment/StatusBox"; +import { apiInvestmentGetByStatus } from "@/service/api-client/api-investment"; +import { useFocusEffect, useLocalSearchParams } from "expo-router"; +import { useCallback, useState } from "react"; +import { RefreshControl } from "react-native"; + +export default function Investment_ScreenPortofolio() { + const { user } = useAuth(); + const { status } = useLocalSearchParams<{ status?: string }>(); + + const [activeCategory, setActiveCategory] = useState( + status || "publish", + ); + + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + if (!user?.id) return { data: [] }; + + return await apiInvestmentGetByStatus({ + authorId: user.id, + status: activeCategory!, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [user?.id, activeCategory], + onError: (error) => + console.error("[ERROR] Fetch investment by status:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: `Tidak ada data ${activeCategory}`, + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 150, + }); + + // Render item investment + const renderInvestmentItem = ({ item }: { item: any }) => ( + + ); + + const handlePress = (item: any) => { + setActiveCategory(item.value); + // Reset pagination saat kategori berubah + pagination.reset(); + }; + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [activeCategory]), + ); + + const tabsComponent = ( + ({ + id: i, + label: e.label, + value: e.value, + }))} + onButtonPress={handlePress} + activeId={activeCategory as any} + /> + ); + + return ( + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + /> + ); +} diff --git a/screens/Invesment/ScreenTransaction.tsx b/screens/Invesment/ScreenTransaction.tsx new file mode 100644 index 0000000..f51fb9c --- /dev/null +++ b/screens/Invesment/ScreenTransaction.tsx @@ -0,0 +1,144 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + BadgeCustom, + BaseBox, + Grid, + Spacing, + StackCustom, + TextCustom, +} from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import NoDataText from "@/components/_ShareComponent/NoDataText"; +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 { apiInvestmentGetInvoice } from "@/service/api-client/api-investment"; +import { GStyles } from "@/styles/global-styles"; +import { formatChatTime } from "@/utils/formatChatTime"; +import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; +import { router, useFocusEffect } from "expo-router"; +import _ from "lodash"; +import { useCallback } from "react"; +import { RefreshControl, View } from "react-native"; + +export default function Investment_ScreenTransaction() { + const { user } = useAuth(); + + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + if (!user?.id) return { data: [] }; + + return await apiInvestmentGetInvoice({ + authorId: user.id, + category: "transaction", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [user?.id], + onError: (error) => console.error("[ERROR] Fetch transaction:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Tidak ada transaksi", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); + + 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 = ({ id, status }: { id: string; status: string }) => { + if (status === "menunggu") { + router.push(`/investment/${id}/(transaction-flow)/invoice`); + } else if (status === "proses") { + router.push(`/investment/${id}/(transaction-flow)/process`); + } else if (status === "berhasil") { + router.push(`/investment/${id}/(transaction-flow)/success`); + } else if (status === "gagal") { + router.push(`/investment/${id}/(transaction-flow)/failed`); + } + }; + + // Render item transaksi + const renderTransactionItem = ({ item }: { item: any }) => ( + { + handlePress({ + id: item.id, + status: _.lowerCase(item.statusInvoice), + }); + }} + > + + + + {item?.title || "-"} + + {formatChatTime(item?.createdAt)} + + + + + + + + + + Rp. {formatCurrencyDisplay(item?.nominal) || "-"} + + + {item?.statusInvoice || "-"} + + + + + + ); + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [user?.id]) + ); + + return ( + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + /> + ); +} diff --git a/service/api-client/api-investment.ts b/service/api-client/api-investment.ts index 25e147f..95c3c84 100644 --- a/service/api-client/api-investment.ts +++ b/service/api-client/api-investment.ts @@ -14,13 +14,15 @@ export async function apiInvestmentCreate({ data }: { data: any }) { export async function apiInvestmentGetByStatus({ authorId, status, + page = "1", }: { authorId: string; status: string; + page?: string; }) { try { const response = await apiConfig.get( - `/mobile/investment/${authorId}/${status}` + `/mobile/investment/${authorId}/${status}?page=${page}` ); return response.data; } catch (error) { @@ -103,13 +105,15 @@ export async function apiInvestmentUpsertDocument({ export async function apiInvestmentGetDocument({ id, category, + page = "1", }: { id: string; category: "one-document" | "all-document"; + page?: string; }) { try { const response = await apiConfig.get( - `/mobile/investment/${id}/document?category=${category}` + `/mobile/investment/${id}/document?category=${category}&page=${page}` ); return response.data; } catch (error) { @@ -131,15 +135,17 @@ export async function apiInvestmentDeleteDocument({ id }: { id: string }) { export async function apiInvestmentGetAll({ category, authorId, + page = "1", }: { category: "my-holding" | "bursa"; authorId?: string; + page?: string; }) { try { const response = await apiConfig.get( `/mobile/investment?category=${category}${ authorId ? `&authorId=${authorId}` : "" - }` + }&page=${page}` ); return response.data; } catch (error) { @@ -168,16 +174,19 @@ export async function apiInvestmentGetInvoice({ id, authorId, category, + page = "1", }: { id?: string; authorId?: string; category: "my-invest" | "transaction" | "invoice"; + page?: string; }) { const categoryQuery = `?category=${category}`; const authorIdQuery = authorId ? `&authorId=${authorId}` : ""; + const pageQuery = `&page=${page}`; try { const response = await apiConfig.get( - `/mobile/investment/${id}/invoice${categoryQuery}${authorIdQuery}` + `/mobile/investment/${id}/invoice${categoryQuery}${authorIdQuery}${pageQuery}` ); return response.data; } catch (error) {