From 38a6b424e879567048df27e4b39fec35a9fb148f Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Mon, 9 Feb 2026 14:38:55 +0800 Subject: [PATCH 1/2] Ringkasan Perubahan 1. Pembuatan dan Pembaruan Komponen Layar Investasi dengan Sistem Pagination ScreenMyHolding.tsx - Diperbarui dari sistem loading data statis ke sistem pagination dinamis - Menggunakan hook usePagination untuk manajemen data - Mengganti ViewWrapper dengan NewWrapper untuk tampilan yang lebih modern - Menambahkan fitur pull-to-refresh dan infinite scroll - Menggunakan helper pagination dari createPaginationComponents ScreenInvestor.tsx - Dibuat baru dengan sistem pagination - Menggunakan hook usePagination untuk manajemen data - Menggunakan NewWrapper sebagai komponen dasar - Menambahkan fitur pull-to-refresh dan infinite scroll ScreenRecapOfNews.tsx - Dibuat baru dengan sistem pagination - Menggunakan hook usePagination untuk manajemen data - Menggunakan NewWrapper sebagai komponen dasar - Menambahkan fitur pull-to-refresh dan infinite scroll ScreenListOfNews.tsx - Dibuat baru dengan sistem pagination - Menggunakan hook usePagination untuk manajemen data - Menggunakan NewWrapper sebagai komponen dasar - Menambahkan fitur pull-to-refresh dan infinite scroll 2. Perubahan pada Fungsi-Fungsi API untuk Mendukung Pagination apiInvestmentGetAll - Sudah mendukung parameter page dengan default "1" apiInvestmentGetInvestorById - Ditambahkan parameter page dengan default "1" - Memungkinkan pengambilan data investor secara bertahap apiInvestmentGetNews - Ditambahkan parameter page dengan default "1" - Memungkinkan pengambilan data berita secara bertahap 3. Pembaruan File-File di Direktori app/ untuk Menggunakan Komponen-Komponen Baru app/(application)/(user)/investment/[id]/investor.tsx - Diperbarui untuk menggunakan komponen Investment_ScreenInvestor - Menghilangkan logika lokal dan menggantinya dengan komponen modular app/(application)/(user)/investment/[id]/(news)/recap-of-news.tsx - Diperbarui untuk menggunakan komponen Investment_ScreenRecapOfNews - Menghilangkan logika lokal dan menggantinya dengan komponen modular app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx - Diperbarui untuk menggunakan komponen Investment_ScreenListOfNews - Menghilangkan logika lokal dan menggantinya dengan komponen modular 4. Implementasi Sistem Pagination usePagination Hook - Digunakan secara konsisten di semua komponen layar investasi - Menyediakan fitur load more dan refresh - Mengelola state loading, refreshing, dan data NewWrapper Component - Digunakan sebagai pengganti ViewWrapper - Menyediakan dukungan bawaan untuk FlatList - Menyediakan dukungan untuk refreshControl dan onEndReached Pagination Helpers - Menggunakan createPaginationComponents untuk menghasilkan komponen-komponen pagination - Menyediakan skeleton loading saat data sedang dimuat - Menyediakan pesan kosong saat tidak ada data 5. Manfaat dari Perubahan Ini 1. Performa Lebih Baik: Dengan pagination, hanya sejumlah kecil data yang dimuat pada satu waktu, meningkatkan performa aplikasi 2. Pengalaman Pengguna Lebih Baik: Fitur pull-to-refresh dan infinite scroll membuat navigasi lebih intuitif 3. Kode Lebih Modular: Komponen-komponen baru dapat digunakan kembali dan dipelihara lebih mudah 4. Struktur Kode Lebih Rapi: Pemisahan antara logika tampilan dan logika data membuat kode lebih terorganisir Co-authored-by: Qwen-Coder --- QWEN.md | 169 +++++++++++++ .../(user)/investment/(tabs)/my-holding.tsx | 81 +----- .../[id]/(document)/recap-of-document.tsx | 4 +- .../investment/[id]/(news)/list-of-news.tsx | 98 +------- .../investment/[id]/(news)/recap-of-news.tsx | 99 +------- .../[id]/(transaction-flow)/invoice.tsx | 4 +- .../(user)/investment/[id]/investor.tsx | 65 +---- docs/prompt-for-qwen-code.md | 35 ++- ...eenRecap.tsx => ScreenRecapOfDocument.tsx} | 11 +- screens/Invesment/News/BoxNews.tsx | 13 + screens/Invesment/News/ScreenListOfNews.tsx | 109 +++++++++ screens/Invesment/News/ScreenRecapOfNews.tsx | 110 +++++++++ screens/Invesment/ScreenInvestor.tsx | 84 +++++++ screens/Invesment/ScreenInvoice.tsx | 230 ++++++++++++++++++ screens/Invesment/ScreenMyHolding.tsx | 91 +++++++ screens/Invesment/ScreenTransaction.tsx | 2 + service/api-client/api-investment.ts | 8 +- 17 files changed, 868 insertions(+), 345 deletions(-) rename screens/Invesment/Document/{ScreenRecap.tsx => ScreenRecapOfDocument.tsx} (96%) create mode 100644 screens/Invesment/News/BoxNews.tsx create mode 100644 screens/Invesment/News/ScreenListOfNews.tsx create mode 100644 screens/Invesment/News/ScreenRecapOfNews.tsx create mode 100644 screens/Invesment/ScreenInvestor.tsx create mode 100644 screens/Invesment/ScreenInvoice.tsx create mode 100644 screens/Invesment/ScreenMyHolding.tsx diff --git a/QWEN.md b/QWEN.md index e69de29..41c3cd5 100644 --- a/QWEN.md +++ b/QWEN.md @@ -0,0 +1,169 @@ +# HIPMI Mobile Application - Development Context + +## Project Overview + +HIPMI Mobile is a cross-platform mobile application built with Expo and React Native. The application is named "HIPMI Badung Connect" and serves as a platform for the HIPMI (Himpunan Pengusaha dan Pengusaha Indonesia) Badung chapter. It's designed to run on iOS, Android, and web platforms using a single codebase. + +### Key Technologies +- **Framework**: Expo (v54.0.0) with React Native (v0.81.4) +- **Language**: TypeScript +- **Architecture**: File-based routing with Expo Router +- **State Management**: Context API +- **UI Components**: React Native Paper, custom components +- **Maps Integration**: Mapbox Maps for React Native +- **Push Notifications**: React Native Firebase Messaging +- **Build System**: Metro bundler + +### Project Structure +``` +hipmi-mobile/ +├── app/ # Main application screens and routing +│ ├── _layout.tsx # Root layout component +│ ├── index.tsx # Entry point (Login screen) +│ └── ... +├── components/ # Reusable UI components +├── context/ # State management (AuthContext) +├── screens/ # Screen components organized by feature +│ ├── Admin/ # Admin panel screens +│ ├── Authentication/ # Login, registration flows +│ ├── Collaboration/ # Collaboration features +│ ├── Event/ # Event management +│ ├── Forum/ # Forum functionality +│ ├── Home/ # Home screen +│ ├── Maps/ # Map integration +│ ├── Profile/ # User profile +│ └── ... +├── assets/ # Images, icons, and static assets +├── constants/ # Constants and configuration values +├── hooks/ # Custom React hooks +├── lib/ # Utility libraries +├── navigation/ # Navigation configuration +├── service/ # API services and business logic +├── types/ # TypeScript type definitions +└── utils/ # Helper functions +``` + +## Building and Running + +### Prerequisites +- Node.js (with bun as the package manager) +- Expo CLI +- iOS Simulator or Android Emulator (for native builds) + +### Setup and Development + +1. **Install Dependencies** + ```bash + bun install + ``` + +2. **Run Development Server** + ```bash + bun run start + ``` + Or use the shorthand: + ```bash + bunx expo start + ``` + +3. **Platform-Specific Commands** + - iOS: `bun run ios` or `bunx expo start --ios` + - Android: `bun run android` or `bunx expo start --android` + - Web: `bun run web` or `bunx expo start --web` + +4. **Linting** + ```bash + bun run lint + ``` + +### Environment Variables +The application uses environment variables defined in the app.config.js file: +- `API_BASE_URL`: Base URL for API endpoints +- `BASE_URL`: Base application URL +- `DEEP_LINK_URL`: URL for deep linking functionality + +### EAS Build Configuration +The project uses Expo Application Services (EAS) for building and deploying: +- Development builds with development client +- Preview builds for internal distribution +- Production builds for app stores + +## Features and Functionality + +The application appears to include several key modules: +- **Authentication**: Login, registration, and verification flows +- **Admin Panel**: Administrative functions +- **Collaboration**: Tools for member collaboration +- **Events**: Event management and calendar +- **Forum**: Discussion forums +- **Maps**: Location-based services with Mapbox integration +- **Donations**: Donation functionality +- **Job Board**: Employment opportunities +- **Investment**: Investment-related features +- **Voting**: Voting systems +- **Portfolio**: Member portfolio showcase +- **Notifications**: Push notifications via Firebase + +## Development Conventions + +### Coding Standards +- TypeScript is used throughout the project for type safety +- Component-based architecture with reusable components +- Context API for state management +- File-based routing with Expo Router +- Consistent naming conventions using camelCase for variables and PascalCase for components + +### Testing +- Linting is configured with ESLint +- Standard Expo linting configuration is used + +### Security +- Firebase is integrated for authentication and messaging +- Camera and location permissions are properly configured +- Deep linking is secured with app domain associations + +## Key Dependencies + +### Core Dependencies +- `@react-navigation/*`: Navigation solution for React Native +- `@react-native-firebase/*`: Firebase integration for React Native +- `@rnmapbox/maps`: Mapbox integration for React Native +- `expo-router`: File-based routing for Expo applications +- `react-native-paper`: Material Design components for React Native +- `react-native-toast-message`: Toast notifications +- `react-native-otp-entry`: OTP input components +- `react-native-qrcode-svg`: QR code generation + +### Development Dependencies +- `@types/*`: TypeScript type definitions +- `eslint-config-expo`: Expo-specific ESLint configuration +- `typescript`: Type checking + +## Platform Support + +The application is configured to support: +- **iOS**: With tablet support and proper permissions +- **Android**: With adaptive icons and intent filters for deep linking +- **Web**: Static output configuration for web deployment + +## Special Configurations + +### iOS Configuration +- Bundle identifier: `com.anonymous.hipmi-mobile` +- Supports tablets +- Google Services integration +- Location permission handling +- Associated domains for deep linking + +### Android Configuration +- Package name: `com.bip.hipmimobileapp` +- Adaptive icons +- Edge-to-edge display enabled +- Intent filters for HTTPS deep linking +- Google Services integration + +### Maps Integration +The application uses Mapbox for mapping functionality with the `@rnmapbox/maps` plugin. + +### Push Notifications +Firebase Cloud Messaging is integrated for push notifications with proper configuration for both iOS and Android platforms. \ No newline at end of file diff --git a/app/(application)/(user)/investment/(tabs)/my-holding.tsx b/app/(application)/(user)/investment/(tabs)/my-holding.tsx index 5a78aae..386f0b5 100644 --- a/app/(application)/(user)/investment/(tabs)/my-holding.tsx +++ b/app/(application)/(user)/investment/(tabs)/my-holding.tsx @@ -1,83 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - BaseBox, - Grid, - LoaderCustom, - ProgressCustom, - Spacing, - StackCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import NoDataText from "@/components/_ShareComponent/NoDataText"; -import { useAuth } from "@/hooks/use-auth"; -import { apiInvestmentGetAll } from "@/service/api-client/api-investment"; -import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; -import { router, useFocusEffect } from "expo-router"; -import _ from "lodash"; -import React, { useCallback, useState } from "react"; -import { View } from "react-native"; +import Investment_ScreenMyHolding from "@/screens/Invesment/ScreenMyHolding"; export default function InvestmentMyHolding() { - const { user } = useAuth(); - const [list, setList] = useState(null); - const [loadingList, setLoadingList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadList(); - }, [user?.id]) - ); - - const onLoadList = async () => { - try { - setLoadingList(true); - const response = await apiInvestmentGetAll({ - category: "my-holding", - authorId: user?.id, - }); - console.log("[DATA LIST]", JSON.stringify(response.data, null, 2)); - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadingList(false); - } - }; - return ( - - {loadingList ? ( - - ) : _.isEmpty(list) ? ( - - ) : ( - list?.map((item, index) => ( - - router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`) - } - > - - {item?.title} - - Rp. {formatCurrencyDisplay(item?.nominal)} - - {item?.lembarTerbeli} Lembar - - - - )) - )} - + <> + + ); } 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 5000a92..677d764 100644 --- a/app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx +++ b/app/(application)/(user)/investment/[id]/(document)/recap-of-document.tsx @@ -1,10 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import Investment_ScreenRecap from "@/screens/Invesment/Document/ScreenRecap"; +import Investment_ScreenRecapOfDocument from "@/screens/Invesment/Document/ScreenRecapOfDocument"; export default function InvestmentRecapOfDocument() { return ( <> - + ); } diff --git a/app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx b/app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx index 8dd03cc..887b8da 100644 --- a/app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx +++ b/app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx @@ -1,100 +1,10 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - BackButton, - BaseBox, - DrawerCustom, - LoaderCustom, - MenuDrawerDynamicGrid, - TextCustom, - ViewWrapper, -} from "@/components"; -import { IconPlus } from "@/components/_Icon"; -import { apiInvestmentGetNews } from "@/service/api-client/api-investment"; -import { - router, - Stack, - useFocusEffect, - useLocalSearchParams, -} from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Investment_ScreenListOfNews from "@/screens/Invesment/News/ScreenListOfNews"; +import { useLocalSearchParams } from "expo-router"; export default function InvestmentListOfNews() { 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 apiInvestmentGetNews({ - id: id as string, - category: "all-news", - }); - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadList(false); - } - }; + return ( - <> - , - // headerRight: () => setOpenDrawer(true)} />, - }} - /> - - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Tidak ada data - - ) : ( - list?.map((item: any, index: number) => ( - - {item.title} - - )) - )} - - - setOpenDrawer(false)} - height={"auto"} - > - , - }, - ]} - onPressItem={(item) => { - router.push(item.path as any); - setOpenDrawer(false); - }} - /> - - + ); } diff --git a/app/(application)/(user)/investment/[id]/(news)/recap-of-news.tsx b/app/(application)/(user)/investment/[id]/(news)/recap-of-news.tsx index f4b36ab..cd0af4a 100644 --- a/app/(application)/(user)/investment/[id]/(news)/recap-of-news.tsx +++ b/app/(application)/(user)/investment/[id]/(news)/recap-of-news.tsx @@ -1,101 +1,10 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - BackButton, - BaseBox, - DotButton, - DrawerCustom, - LoaderCustom, - MenuDrawerDynamicGrid, - TextCustom, - ViewWrapper, -} from "@/components"; -import { IconPlus } from "@/components/_Icon"; -import { apiInvestmentGetNews } from "@/service/api-client/api-investment"; -import { - router, - Stack, - useFocusEffect, - useLocalSearchParams, -} from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Investment_ScreenRecapOfNews from "@/screens/Invesment/News/ScreenRecapOfNews"; +import { useLocalSearchParams } from "expo-router"; export default function InvestmentRecapOfNews() { 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 apiInvestmentGetNews({ - id: id as string, - category: "all-news", - }); - - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadList(false); - } - }; - + return ( - <> - , - headerRight: () => setOpenDrawer(true)} />, - }} - /> - - {loadList ? ( - - ) : _.isEmpty(list) ? ( - - Tidak ada data - - ) : ( - list?.map((item: any, index: number) => ( - - {item.title} - - )) - )} - - - setOpenDrawer(false)} - height={"auto"} - > - , - }, - ]} - onPressItem={(item) => { - router.push(item.path as any); - setOpenDrawer(false); - }} - /> - - + ); } diff --git a/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx b/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx index ecdd0bc..722d229 100644 --- a/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx +++ b/app/(application)/(user)/investment/[id]/(transaction-flow)/invoice.tsx @@ -1,10 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import Investment_ScreenTransaction from "@/screens/Invesment/ScreenTransaction"; +import Investment_ScreenInvoice from "@/screens/Invesment/ScreenInvoice"; export default function InvestmentInvoice() { return ( <> - + ); } diff --git a/app/(application)/(user)/investment/[id]/investor.tsx b/app/(application)/(user)/investment/[id]/investor.tsx index a64abb3..1450010 100644 --- a/app/(application)/(user)/investment/[id]/investor.tsx +++ b/app/(application)/(user)/investment/[id]/investor.tsx @@ -1,67 +1,10 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -import { - AvatarUsernameAndOtherComponent, - BoxWithHeaderSection, - LoaderCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import NoDataText from "@/components/_ShareComponent/NoDataText"; -import { apiInvestmentGetInvestorById } from "@/service/api-client/api-investment"; -import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; -import { useFocusEffect, useLocalSearchParams } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Investment_ScreenInvestor from "@/screens/Invesment/ScreenInvestor"; +import { useLocalSearchParams } from "expo-router"; export default function InvestmentInvestor() { const { id } = useLocalSearchParams(); - const [list, setList] = useState(null); - const [loadingList, setLoadingList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadList(); - }, [id]) - ); - - const onLoadList = async () => { - try { - setLoadingList(true); - const response = await apiInvestmentGetInvestorById({ - id: id as string, - }) - console.log("[DATA LIST]", JSON.stringify(response.data, null, 2)); - setList(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadingList(false); - } - } - - + return ( - <> - - {loadingList ? ( - - ) : _.isEmpty(list) ? ( - - ) : ( - list?.map((item: any, index: number) => ( - - - - Rp. {formatCurrencyDisplay(item?.nominal)} - - - )) - )} - - + ); } diff --git a/docs/prompt-for-qwen-code.md b/docs/prompt-for-qwen-code.md index e0eff8f..8fdc268 100644 --- a/docs/prompt-for-qwen-code.md +++ b/docs/prompt-for-qwen-code.md @@ -1,5 +1,34 @@ +File source: app/(application)/(user)/investment/[id]/(news)/list-of-news.tsx +Folder tujuan: screens/Invesment +Nama file utama: ScreenListOfNews.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" +Selanjutnya terapkan pagination pada file "Nama file utama" + +Function fecth: apiInvestmentGetNews +File function fetch: service/api-client/api-investment.ts +File komponen wrapper: components/_ShareComponent/NewWrapper.tsx + +Terapkan pagination pada file "Nama file utama" +Analisa juga file "Nama file utama" , jika belum menggunakan NewWrapper pada file "File komponen wrapper" , maka terapkan juga dan ganti wrapper lama yaitu komponen ViewWrapper + +Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx + +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. + + +File refrensi: screens/Event/ScreenStatus.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 @@ -15,11 +44,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: screens/Event/ScreenStatus.tsx -Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama - - + Terapkan NewWrapper pada file: screens/Forum/DetailForum.tsx diff --git a/screens/Invesment/Document/ScreenRecap.tsx b/screens/Invesment/Document/ScreenRecapOfDocument.tsx similarity index 96% rename from screens/Invesment/Document/ScreenRecap.tsx rename to screens/Invesment/Document/ScreenRecapOfDocument.tsx index 3b762fd..f84de59 100644 --- a/screens/Invesment/Document/ScreenRecap.tsx +++ b/screens/Invesment/Document/ScreenRecapOfDocument.tsx @@ -4,14 +4,12 @@ import { BackButton, DotButton, DrawerCustom, - MenuDrawerDynamicGrid, - TextCustom, + MenuDrawerDynamicGrid } from "@/components"; -import NewWrapper from "@/components/_ShareComponent/NewWrapper"; import { IconEdit } from "@/components/_Icon"; -import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; import { MainColor } from "@/constants/color-palet"; -import { ICON_SIZE_SMALL } from "@/constants/constans-value"; +import { ICON_SIZE_SMALL, 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"; @@ -26,12 +24,11 @@ import { 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() { +export default function Investment_ScreenRecapOfDocument() { const { id } = useLocalSearchParams(); const [openDrawer, setOpenDrawer] = useState(false); const [openDrawerBox, setOpenDrawerBox] = useState(false); diff --git a/screens/Invesment/News/BoxNews.tsx b/screens/Invesment/News/BoxNews.tsx new file mode 100644 index 0000000..fbc7a7f --- /dev/null +++ b/screens/Invesment/News/BoxNews.tsx @@ -0,0 +1,13 @@ +import { BaseBox, Spacing, TextCustom } from "@/components"; + +export default function Investment_BoxNews({ item }: { item: { id: string; title: string } }) { + return ( + <> + + + {item.title} + + + + ); +} diff --git a/screens/Invesment/News/ScreenListOfNews.tsx b/screens/Invesment/News/ScreenListOfNews.tsx new file mode 100644 index 0000000..37da0d7 --- /dev/null +++ b/screens/Invesment/News/ScreenListOfNews.tsx @@ -0,0 +1,109 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + BackButton, + BaseBox, + DrawerCustom, + LoaderCustom, + MenuDrawerDynamicGrid, + TextCustom, +} from "@/components"; +import { IconPlus } from "@/components/_Icon"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiInvestmentGetNews } from "@/service/api-client/api-investment"; +import { router, Stack, useFocusEffect } from "expo-router"; +import { useCallback, useState } from "react"; +import { RefreshControl } from "react-native"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import Investment_BoxNews from "./BoxNews"; + +interface InvestmentListOfNewsProps { + investmentId: string; +} + +export default function Investment_ScreenListOfNews({ + investmentId, +}: InvestmentListOfNewsProps) { + const [openDrawer, setOpenDrawer] = useState(false); + + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiInvestmentGetNews({ + id: investmentId, + category: "all-news", + page: String(page), + }); + }, + pageSize: 10, // Sesuaikan dengan jumlah item per halaman dari API + dependencies: [investmentId], + }); + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [investmentId]), + ); + + 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 berita", + skeletonCount: 5, + skeletonHeight: 80, + loadingFooterText: "Memuat lebih banyak berita...", + }); + + return ( + <> + , + // headerRight: () => setOpenDrawer(true)} />, + }} + /> + + + } + /> + + setOpenDrawer(false)} + height={"auto"} + > + , + }, + ]} + onPressItem={(item) => { + router.push(item.path as any); + setOpenDrawer(false); + }} + /> + + + ); +} diff --git a/screens/Invesment/News/ScreenRecapOfNews.tsx b/screens/Invesment/News/ScreenRecapOfNews.tsx new file mode 100644 index 0000000..157ca9b --- /dev/null +++ b/screens/Invesment/News/ScreenRecapOfNews.tsx @@ -0,0 +1,110 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + BackButton, + BaseBox, + DotButton, + DrawerCustom, + LoaderCustom, + MenuDrawerDynamicGrid, + Spacing, + TextCustom, +} from "@/components"; +import { IconPlus } from "@/components/_Icon"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiInvestmentGetNews } from "@/service/api-client/api-investment"; +import { router, Stack, useFocusEffect } from "expo-router"; +import { useCallback, useState } from "react"; +import { RefreshControl } from "react-native"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import Investment_BoxNews from "./BoxNews"; + +interface InvestmentRecapOfNewsProps { + investmentId: string; +} + +export default function Investment_ScreenRecapOfNews({ + investmentId, +}: InvestmentRecapOfNewsProps) { + const [openDrawer, setOpenDrawer] = useState(false); + + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiInvestmentGetNews({ + id: investmentId, + category: "all-news", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, // Sesuaikan dengan jumlah item per halaman dari API + dependencies: [investmentId], + }); + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [investmentId]), + ); + + 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 berita", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 80, + }); + + return ( + <> + , + headerRight: () => setOpenDrawer(true)} />, + }} + /> + + } + /> + + setOpenDrawer(false)} + height={"auto"} + > + , + }, + ]} + onPressItem={(item) => { + router.push(item.path as any); + setOpenDrawer(false); + }} + /> + + + ); +} diff --git a/screens/Invesment/ScreenInvestor.tsx b/screens/Invesment/ScreenInvestor.tsx new file mode 100644 index 0000000..d50c54a --- /dev/null +++ b/screens/Invesment/ScreenInvestor.tsx @@ -0,0 +1,84 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + AvatarUsernameAndOtherComponent, + BoxWithHeaderSection, + CenterCustom, + Spacing, + StackCustom, + 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 { apiInvestmentGetInvestorById } from "@/service/api-client/api-investment"; +import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; +import { RefreshControl } from "react-native"; + +interface InvestmentInvestorProps { + investmentId: string; +} + +export default function Investment_ScreenInvestor({ + investmentId, +}: InvestmentInvestorProps) { + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiInvestmentGetInvestorById({ + id: investmentId, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [investmentId], + onError: (error) => { + console.error("Error fetching investors:", error); + }, + }); + + const renderItem = ({ item }: { item: any }) => ( + + + + + + Rp. {formatCurrencyDisplay(item?.nominal)} + + + + + + ); + + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: "Tidak ada investor", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 120, + }); + + return ( + + } + /> + ); +} diff --git a/screens/Invesment/ScreenInvoice.tsx b/screens/Invesment/ScreenInvoice.tsx new file mode 100644 index 0000000..849a711 --- /dev/null +++ b/screens/Invesment/ScreenInvoice.tsx @@ -0,0 +1,230 @@ +/* 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"; + +export default function Investment_ScreenInvoice() { + 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/screens/Invesment/ScreenMyHolding.tsx b/screens/Invesment/ScreenMyHolding.tsx new file mode 100644 index 0000000..1e643d5 --- /dev/null +++ b/screens/Invesment/ScreenMyHolding.tsx @@ -0,0 +1,91 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + BaseBox, + ProgressCustom, + StackCustom, + 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 { apiInvestmentGetAll } from "@/service/api-client/api-investment"; +import { formatCurrencyDisplay } from "@/utils/formatCurrencyDisplay"; +import { router, useFocusEffect } from "expo-router"; +import { useCallback } from "react"; +import { RefreshControl } from "react-native"; + +export default function Investment_ScreenMyHolding() { + const { user } = useAuth(); + + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiInvestmentGetAll({ + category: "my-holding", + authorId: user?.id, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [user?.id], + onError: (error) => console.error("[ERROR] Fetch my holding:", error), + }); + + useFocusEffect( + useCallback(() => { + pagination.onRefresh(); + }, [user?.id]), + ); + + const renderItem = ({ item, index }: { item: any; index: number }) => ( + + router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`) + } + > + + {item?.title} + Rp. {formatCurrencyDisplay(item?.nominal)} + {item?.lembarTerbeli} Lembar + + + + ); + + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + isInitialLoad: pagination.isInitialLoad, + emptyMessage: "Tidak ada investasi yang dimiliki", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 120, + }); + + return ( + + } + hideFooter + /> + ); +} diff --git a/screens/Invesment/ScreenTransaction.tsx b/screens/Invesment/ScreenTransaction.tsx index f51fb9c..d010f84 100644 --- a/screens/Invesment/ScreenTransaction.tsx +++ b/screens/Invesment/ScreenTransaction.tsx @@ -65,6 +65,8 @@ export default function Investment_ScreenTransaction() { }; const handlePress = ({ id, status }: { id: string; status: string }) => { + console.log("ID", id); + console.log("Status", status); if (status === "menunggu") { router.push(`/investment/${id}/(transaction-flow)/invoice`); } else if (status === "proses") { diff --git a/service/api-client/api-investment.ts b/service/api-client/api-investment.ts index 95c3c84..e4df5b9 100644 --- a/service/api-client/api-investment.ts +++ b/service/api-client/api-investment.ts @@ -238,13 +238,15 @@ export async function apiInvestmentCreateNews({ export async function apiInvestmentGetNews({ id, category, + page = "1", }: { id: string; category: "all-news" | "one-news"; + page?: string; }) { try { const response = await apiConfig.get( - `/mobile/investment/${id}/news?category=${category}` + `/mobile/investment/${id}/news?category=${category}&page=${page}` ); return response.data; } catch (error) { @@ -263,11 +265,13 @@ export async function apiInvestmentDeleteNews({ id }: { id: string }) { export async function apiInvestmentGetInvestorById({ id, + page = "1", }: { id: string; + page?: string; }) { try { - const response = await apiConfig.get(`/mobile/investment/${id}/investor`); + const response = await apiConfig.get(`/mobile/investment/${id}/investor?page=${page}`); return response.data; } catch (error) { throw error; -- 2.49.1 From 2705f96b014e1ada7409dfa2d9eaf05494d28705 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Mon, 9 Feb 2026 17:35:54 +0800 Subject: [PATCH 2/2] 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) { -- 2.49.1