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-coder@alibabacloud.com>
This commit is contained in:
2026-02-09 14:38:55 +08:00
parent 83fa277e03
commit 38a6b424e8
17 changed files with 868 additions and 345 deletions

View File

@@ -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<any[] | null>(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 (
<ViewWrapper hideFooter>
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<NoDataText />
) : (
list?.map((item, index) => (
<BaseBox
key={index}
paddingTop={7}
paddingBottom={7}
onPress={() =>
router.push(`/investment/${item?.id}/(my-holding)/${item?.id}`)
}
>
<StackCustom>
<TextCustom truncate={2}>{item?.title}</TextCustom>
<TextCustom>
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
<TextCustom>{item?.lembarTerbeli} Lembar</TextCustom>
<ProgressCustom
label={`${item.progress}%`}
value={Number(item.progress)}
size="lg"
animated
color="primary"
/>
</StackCustom>
</BaseBox>
))
)}
</ViewWrapper>
<>
<Investment_ScreenMyHolding />
</>
);
}

View File

@@ -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 (
<>
<Investment_ScreenRecap />
<Investment_ScreenRecapOfDocument />
</>
);
}

View File

@@ -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<any[] | null>(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 (
<>
<Stack.Screen
options={{
title: "Daftar Berita",
headerLeft: () => <BackButton />,
// headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox
key={index}
paddingBlock={5}
href={`/investment/[id]/(news)/${item.id}`}
>
<TextCustom bold>{item.title}</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tambah Berita",
path: `/investment/${id}/add-news`,
icon: <IconPlus />,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</>
<Investment_ScreenListOfNews investmentId={id as string} />
);
}

View File

@@ -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<any[] | null>(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 (
<>
<Stack.Screen
options={{
title: "Rekap Berita",
headerLeft: () => <BackButton />,
headerRight: () => <DotButton onPress={() => setOpenDrawer(true)} />,
}}
/>
<ViewWrapper>
{loadList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<TextCustom align="center" color="gray">
Tidak ada data
</TextCustom>
) : (
list?.map((item: any, index: number) => (
<BaseBox
key={index}
paddingBlock={5}
href={`/investment/[id]/(news)/${item.id}`}
>
<TextCustom bold>{item.title}</TextCustom>
</BaseBox>
))
)}
</ViewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tambah Berita",
path: `/investment/${id}/add-news`,
icon: <IconPlus />,
},
]}
onPressItem={(item) => {
router.push(item.path as any);
setOpenDrawer(false);
}}
/>
</DrawerCustom>
</>
<Investment_ScreenRecapOfNews investmentId={id as string} />
);
}

View File

@@ -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 (
<>
<Investment_ScreenTransaction />
<Investment_ScreenInvoice />
</>
);
}

View File

@@ -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<any[] | null>(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 (
<>
<ViewWrapper>
{loadingList ? (
<LoaderCustom />
) : _.isEmpty(list) ? (
<NoDataText />
) : (
list?.map((item: any, index: number) => (
<BoxWithHeaderSection key={index}>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId}
name={item?.Author?.username}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
/>
<TextCustom bold>
Rp. {formatCurrencyDisplay(item?.nominal)}
</TextCustom>
</BoxWithHeaderSection>
))
)}
</ViewWrapper>
</>
<Investment_ScreenInvestor investmentId={id as string} />
);
}