Fix semua tampilan yang memiliki fungsi infitine load

UI – User Notifications
- app/(application)/(user)/notifications/index.tsx
- service/api-notifications.ts
- screens/Notification/

UI – Portofolio (User)
- app/(application)/(user)/portofolio/[id]/create.tsx
- app/(application)/(user)/portofolio/[id]/edit.tsx
- app/(application)/(user)/portofolio/[id]/list.tsx
- screens/Portofolio/BoxPortofolioView.tsx
- screens/Portofolio/ViewListPortofolio.tsx
- screens/Profile/PortofolioSection.tsx
- service/api-client/api-portofolio.ts

Forum & User Search
- screens/Forum/DetailForum2.tsx
- screens/Forum/ViewBeranda3.tsx
- screens/UserSeach/MainView_V2.tsx

Constants & Docs
- constants/constans-value.ts
- docs/prompt-for-qwen-code.md

### No Issue
This commit is contained in:
2026-01-30 17:18:47 +08:00
parent ed16f1b204
commit ec79a1fbcd
15 changed files with 411 additions and 326 deletions

View File

@@ -1,248 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */ import ScreenNotification from "@/screens/Notification/ScreenNotification";
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => { export default function Notification() {
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value
);
return category?.label;
};
const fixPath = ({
deepLink,
categoryApp,
}: {
deepLink: string;
categoryApp: string;
}) => {
if (categoryApp === "OTHER") {
return deepLink;
}
const separator = deepLink.includes("?") ? "&" : "?";
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
categoryApp
)}`;
console.log("Fix Path", fixedPath);
return fixedPath;
};
const BoxNotification = ({
data,
activeCategory,
}: {
data: any;
activeCategory: string | null;
}) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
return ( return (
<> <>
<BaseBox <ScreenNotification />
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
// console.log(
// "Notification >",
// selectedCategory(activeCategory as string)
// );
const newPath = fixPath({
deepLink: data.deepLink,
categoryApp: data.kategoriApp,
});
router.navigate(newPath as any);
selectedCategory(activeCategory as string);
if (!data.isRead) {
markAsRead(data.id);
}
}}
>
<StackCustom>
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
</StackCustom>
</BaseBox>
</>
);
};
export default function Notifications() {
const { user } = useAuth();
const { category } = useLocalSearchParams<{ category?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
category || "event"
);
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
useFocusEffect(
useCallback(() => {
fecthData();
}, [activeCategory])
);
const fecthData = async () => {
try {
setLoading(true);
const response = await apiGetNotificationsById({
id: user?.id as any,
category: activeCategory as any,
});
if (response.success) {
setListData(response.data);
} else {
setListData([]);
}
} catch (error) {
console.log("Error Notification", error);
} finally {
setLoading(false);
}
};
const onRefresh = () => {
setRefreshing(true);
fecthData();
setRefreshing(false);
};
return (
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}}
/>
<NewWrapper
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
/>
}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{loading ? (
<ListSkeletonComponent />
) : _.isEmpty(listData) ? (
<NoDataText text="Belum ada notifikasi" />
) : (
listData.map((e, i) => (
<View key={i}>
<BoxNotification
data={e}
activeCategory={activeCategory as any}
/>
</View>
))
)}
</NewWrapper>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tandai Semua Dibaca",
value: "read-all",
icon: (
<Ionicons
name="reader-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
path: "",
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
message:
"Apakah Anda yakin ingin menandai semua notifikasi dibaca?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
const data = _.cloneDeep(listData);
data.forEach((e) => {
e.isRead = true;
});
setListData(data);
onRefresh();
setOpenDrawer(false);
},
});
}
}}
/>
</DrawerCustom>
</> </>
); );
} }

View File

@@ -7,6 +7,7 @@ import {
CenterCustom, CenterCustom,
Grid, Grid,
InformationBox, InformationBox,
NewWrapper,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
@@ -120,7 +121,7 @@ export default function PortofolioCreate() {
}; };
return ( return (
<ViewWrapper <NewWrapper
footerComponent={ footerComponent={
<Portofolio_ButtonCreate <Portofolio_ButtonCreate
id={id as string} id={id as string}
@@ -357,8 +358,8 @@ export default function PortofolioCreate() {
setDataMedsos({ ...dataMedsos, youtube: value }) setDataMedsos({ ...dataMedsos, youtube: value })
} }
/> />
<Spacing /> {/* <Spacing /> */}
</StackCustom> </StackCustom>
</ViewWrapper> </NewWrapper>
); );
} }

View File

@@ -4,14 +4,15 @@ import {
BoxButtonOnFooter, BoxButtonOnFooter,
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
NewWrapper,
SelectCustom, SelectCustom,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom, TextCustom,
TextInputCustom, TextInputCustom,
ViewWrapper,
} from "@/components"; } from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_XLARGE } from "@/constants/constans-value"; import { ICON_SIZE_XLARGE } from "@/constants/constans-value";
import { import {
@@ -238,7 +239,7 @@ export default function PortofolioEdit() {
return !dataArray.some( return !dataArray.some(
(item: any) => (item: any) =>
!item.MasterSubBidangBisnis.id || !item.MasterSubBidangBisnis.id ||
item.MasterSubBidangBisnis.id.trim() === "" item.MasterSubBidangBisnis.id.trim() === "",
); );
} }
@@ -319,16 +320,16 @@ export default function PortofolioEdit() {
if (!bidangBisnis || !subBidangBisnis) { if (!bidangBisnis || !subBidangBisnis) {
return ( return (
<> <>
<ViewWrapper> <NewWrapper>
<ActivityIndicator size="large" color={MainColor.yellow} /> <ListSkeletonComponent height={80} />
</ViewWrapper> </NewWrapper>
</> </>
); );
} }
return ( return (
<> <>
<ViewWrapper footerComponent={buttonUpdate}> <NewWrapper footerComponent={buttonUpdate}>
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextInputCustom <TextInputCustom
required required
@@ -471,7 +472,7 @@ export default function PortofolioEdit() {
/> />
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
</ViewWrapper> </NewWrapper>
</> </>
); );
} }

View File

@@ -1,28 +1,9 @@
import { TextCustom, ViewWrapper } from "@/components"; import ViewListPortofolio from "@/screens/Portofolio/ViewListPortofolio";
import Portofolio_BoxView from "@/screens/Portofolio/BoxPortofolioView";
import { apiGetPortofolio } from "@/service/api-client/api-portofolio";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useState } from "react";
export default function ListPortofolio() { export default function ListPortofolio() {
const { id } = useLocalSearchParams();
const [data, setData] = useState<any[]>([]);
useFocusEffect(
useCallback(() => {
onLoadPortofolio(id as string);
}, [id])
);
const onLoadPortofolio = async (id: string) => {
const response = await apiGetPortofolio({ id: id });
setData(response.data);
};
return ( return (
<ViewWrapper> <>
{data ? data?.map((item: any, index: number) => ( <ViewListPortofolio />
<Portofolio_BoxView key={index} data={item} /> </>
)) : <TextCustom>Tidak ada portofolio</TextCustom>}
</ViewWrapper>
); );
} }

View File

@@ -24,7 +24,7 @@ export {
// OS Height // OS Height
const OS_ANDROID_HEIGHT = 115 const OS_ANDROID_HEIGHT = 115
const OS_IOS_HEIGHT = 70 const OS_IOS_HEIGHT = 90
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
// Text Size // Text Size

View File

@@ -1,8 +1,16 @@
<!-- Start Penerapan Pagination --> <!-- Start Penerapan Pagination -->
Terapkan pagination pada file: screens/Forum/DetailForum.tsx
Component yang digunakan: hooks/use-pagination.tsx dan helpers/paginationHelpers.tsx
Perbaiki fetch pada file: service/api-client/api-forum.ts File utama: screens/Notification/ScreenNotification.tsx
Fun fecth: apiGetNotificationsById
File fetch: service/api-notifications.ts
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
Terapkan pagination pada file "File utama"
Analisa juga file "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 "Fun fecth" , pada file "File fetch"
Jika tidak ada props page maka tambahkan props page dan default page: "1" Jika tidak ada props page maka tambahkan props page dan default page: "1"
Gunakan bahasa indonesia pada cli agar saya mudah membacanya. Gunakan bahasa indonesia pada cli agar saya mudah membacanya.

View File

@@ -5,6 +5,7 @@ import {
LoaderCustom, LoaderCustom,
NewWrapper, NewWrapper,
Spacing, Spacing,
StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom, TextCustom,
TextInputCustom, TextInputCustom,
@@ -111,7 +112,6 @@ export default function DetailForum2() {
// Create Commentar // Create Commentar
const handlerCreateCommentar = async () => { const handlerCreateCommentar = async () => {
const cencorContent = censorText(text); const cencorContent = censorText(text);
const newData = { const newData = {
@@ -155,7 +155,10 @@ export default function DetailForum2() {
const headerComponent = () => const headerComponent = () =>
// Box Posting // Box Posting
!data ? ( !data ? (
<CustomSkeleton height={200} /> <StackCustom>
<CustomSkeleton height={200} />
<CustomSkeleton height={100} />
</StackCustom>
) : ( ) : (
<> <>
{/* Area Posting */} {/* Area Posting */}
@@ -199,10 +202,7 @@ export default function DetailForum2() {
); );
// Render individual comment item // Render individual comment item
const renderCommentItem = ({ item }: { item: TypeForum_CommentProps }) => const renderCommentItem = ({ item }: { item: TypeForum_CommentProps }) =>(
!data && !commentPagination.listData ? (
<ListSkeletonComponent />
) : (
<Forum_CommentarBoxSection <Forum_CommentarBoxSection
key={item.id} key={item.id}
data={item} data={item}
@@ -212,7 +212,21 @@ export default function DetailForum2() {
setCommentAuthorId(value.setCommentAuthorId); setCommentAuthorId(value.setCommentAuthorId);
}} }}
/> />
); )
// !data || !commentPagination.listData ? (
// // <ListSkeletonComponent height={120} />
// <LoaderCustom />
// ) : (
// <Forum_CommentarBoxSection
// key={item.id}
// data={item}
// onSetData={(value) => {
// setCommentId(value.setCommentId);
// setOpenDrawerCommentar(value.setOpenDrawer);
// setCommentAuthorId(value.setCommentAuthorId);
// }}
// />
// );
// Generate pagination components using helper // Generate pagination components using helper
const { ListEmptyComponent, ListFooterComponent } = const { ListEmptyComponent, ListFooterComponent } =

View File

@@ -102,7 +102,7 @@ export default function Forum_ViewBeranda3() {
<NewWrapper <NewWrapper
headerComponent={ headerComponent={
<View style={{ paddingHorizontal: 16, paddingTop: 8 }}> <View style={{ paddingTop: 8 }}>
<SearchInput <SearchInput
placeholder="Cari topik diskusi" placeholder="Cari topik diskusi"
onChangeText={_.debounce((text) => setSearch(text), 500)} onChangeText={_.debounce((text) => setSearch(text), 500)}

View File

@@ -0,0 +1,249 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL, PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { usePagination } from "@/hooks/use-pagination";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => {
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value
);
return category?.label;
};
const fixPath = ({
deepLink,
categoryApp,
}: {
deepLink: string;
categoryApp: string;
}) => {
if (categoryApp === "OTHER") {
return deepLink;
}
const separator = deepLink.includes("?") ? "&" : "?";
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
categoryApp
)}`;
console.log("Fix Path", fixedPath);
return fixedPath;
};
const BoxNotification = ({
data,
activeCategory,
}: {
data: any;
activeCategory: string | null;
}) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
return (
<>
<BaseBox
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
// console.log(
// "Notification >",
// selectedCategory(activeCategory as string)
// );
const newPath = fixPath({
deepLink: data.deepLink,
categoryApp: data.kategoriApp,
});
router.navigate(newPath as any);
selectedCategory(activeCategory as string);
if (!data.isRead) {
markAsRead(data.id);
}
}}
>
<StackCustom>
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
</StackCustom>
</BaseBox>
</>
);
};
export default function ScreenNotification() {
const { user } = useAuth();
const { category } = useLocalSearchParams<{ category?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
category || "event"
);
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
// Initialize pagination for notifications
const pagination = usePagination({
fetchFunction: async (page) => {
return await apiGetNotificationsById({
id: user?.id as string,
category: activeCategory as any,
page: String(page), // API expects string
});
},
pageSize: PAGINATION_DEFAULT_TAKE,
dependencies: [activeCategory],
});
useFocusEffect(
useCallback(() => {
// Reset and load first page when category changes
pagination.reset();
pagination.onRefresh();
}, [activeCategory])
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// Reset and load first page when category changes
pagination.reset();
pagination.onRefresh();
};
// Render individual notification item
const renderItem = ({ item }: { item: any }) => (
<View key={item.id}>
<BoxNotification
data={item}
activeCategory={activeCategory as any}
/>
</View>
);
// Generate pagination components using helper
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
isInitialLoad: pagination.isInitialLoad,
emptyMessage: "Belum ada notifikasi",
skeletonCount: 5,
skeletonHeight: 100,
});
return (
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}}
/>
<NewWrapper
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
/>
}
listData={pagination.listData}
renderItem={renderItem}
refreshControl={
<RefreshControl
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
onEndReached={pagination.loadMore}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
/>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tandai Semua Dibaca",
value: "read-all",
icon: (
<Ionicons
name="reader-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
path: "",
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
message:
"Apakah Anda yakin ingin menandai semua notifikasi dibaca?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
// Reset and refresh data after marking all as read
pagination.reset();
pagination.onRefresh();
setOpenDrawer(false);
},
});
}
}}
/>
</DrawerCustom>
</>
);
}

View File

@@ -1,5 +1,5 @@
import { BaseBox, Grid, TextCustom } from "@/components"; import { BaseBox, Grid, TextCustom } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value"; import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router"; import { router } from "expo-router";
@@ -8,7 +8,7 @@ export default function Portofolio_BoxView({ data }: { data: any }) {
return ( return (
<> <>
<BaseBox <BaseBox
style={{ backgroundColor: MainColor.darkblue }} style={{ backgroundColor: AccentColor.blue}}
onPress={() => { onPress={() => {
router.push(`/portofolio/${data?.id}`); router.push(`/portofolio/${data?.id}`);
}} }}

View File

@@ -0,0 +1,74 @@
import { NewWrapper, TextCustom } from "@/components";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { MainColor } from "@/constants/color-palet";
import { usePagination } from "@/hooks/use-pagination";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { apiGetPortofolio } from "@/service/api-client/api-portofolio";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback } from "react";
import { RefreshControl } from "react-native";
import Portofolio_BoxView from "./BoxPortofolioView";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
export default function ViewListPortofolio() {
const { id } = useLocalSearchParams();
// Initialize pagination for portfolio items
const pagination = usePagination({
fetchFunction: async (page) => {
return await apiGetPortofolio({
id: id as string,
page: String(page) // API expects string
});
// return response.data;
},
pageSize: PAGINATION_DEFAULT_TAKE,
dependencies: [id],
});
useFocusEffect(
useCallback(() => {
// Reset and load first page when id changes
pagination.reset();
pagination.onRefresh();
}, [id]),
);
// Render individual portfolio item
const renderItem = ({ item }: { item: any }) => (
<Portofolio_BoxView key={item.id} data={item} />
);
// Generate pagination components using helper
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
isInitialLoad: pagination.isInitialLoad,
emptyMessage: "Tidak ada portofolio",
skeletonCount: 3,
skeletonHeight: 100,
});
return (
<NewWrapper
listData={pagination.listData}
renderItem={renderItem}
refreshControl={
<RefreshControl
// IOS
tintColor={MainColor.yellow}
// Android
colors={[MainColor.yellow]}
progressBackgroundColor={MainColor.yellow}
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
onEndReached={pagination.loadMore}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
/>
);
}

View File

@@ -12,7 +12,7 @@ export default function Profile_PortofolioSection({
}) { }) {
return ( return (
<> <>
<BaseBox> <BaseBox >
<View> <View>
<TextCustom bold size="large" align="center"> <TextCustom bold size="large" align="center">
Portofolio Portofolio

View File

@@ -1,14 +1,17 @@
import { import {
AvatarComp, AvatarComp,
ClickableCustom, ClickableCustom,
Grid, Grid,
NewWrapper, NewWrapper,
StackCustom, StackCustom,
TextCustom, TextCustom,
TextInputCustom TextInputCustom,
} from "@/components"; } from "@/components";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL, PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; import {
ICON_SIZE_SMALL,
PAGINATION_DEFAULT_TAKE,
} from "@/constants/constans-value";
import { usePagination } from "@/hooks/use-pagination"; import { usePagination } from "@/hooks/use-pagination";
import { apiAllUser } from "@/service/api-client/api-user"; import { apiAllUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
@@ -18,7 +21,6 @@ import { useCallback, useRef, useState } from "react";
import { RefreshControl, View } from "react-native"; import { RefreshControl, View } from "react-native";
import { createPaginationComponents } from "@/helpers/paginationHelpers"; import { createPaginationComponents } from "@/helpers/paginationHelpers";
export default function UserSearchMainView_V2() { export default function UserSearchMainView_V2() {
const isInitialMount = useRef(true); const isInitialMount = useRef(true);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@@ -30,7 +32,7 @@ export default function UserSearchMainView_V2() {
hasMore, hasMore,
onRefresh, onRefresh,
loadMore, loadMore,
isInitialLoad isInitialLoad,
} = usePagination({ } = usePagination({
fetchFunction: async (page, searchQuery) => { fetchFunction: async (page, searchQuery) => {
const response = await apiAllUser({ const response = await apiAllUser({
@@ -83,7 +85,7 @@ export default function UserSearchMainView_V2() {
padding: 12, padding: 12,
marginBottom: 10, marginBottom: 10,
elevation: 2, elevation: 2,
shadowColor: '#000', shadowColor: "#000",
shadowOffset: { width: 0, height: 1 }, shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2, shadowOpacity: 0.2,
shadowRadius: 2, shadowRadius: 2,
@@ -129,18 +131,19 @@ export default function UserSearchMainView_V2() {
</View> </View>
); );
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({ const { ListEmptyComponent, ListFooterComponent } =
loading, createPaginationComponents({
refreshing, loading,
listData, refreshing,
searchQuery: search, listData,
emptyMessage: "Tidak ada pengguna ditemukan", searchQuery: search,
emptySearchMessage: "Tidak ada hasil pencarian", emptyMessage: "Tidak ada pengguna ditemukan",
skeletonCount: 5, emptySearchMessage: "Tidak ada hasil pencarian",
skeletonHeight: 150, skeletonCount: 5,
loadingFooterText: "Memuat lebih banyak pengguna...", skeletonHeight: 150,
isInitialLoad loadingFooterText: "Memuat lebih banyak pengguna...",
}); isInitialLoad,
});
return ( return (
<> <>
@@ -161,14 +164,4 @@ export default function UserSearchMainView_V2() {
/> />
</> </>
); );
// return (
// <>
// <ViewWrapper>
// <View style={{ padding: 16 }}>
// <TextCustom>{JSON.stringify(listData, null, 2)}</TextCustom>
// </View>
// </ViewWrapper>
// </>
// );
} }

View File

@@ -12,9 +12,9 @@ export async function apiPortofolioCreate({ data }: { data: any }) {
} }
} }
export async function apiGetPortofolio({ id }: { id: string }) { export async function apiGetPortofolio({ id, page = "1" }: { id: string; page?: string }) {
try { try {
const response = await apiConfig.get(`/mobile/portofolio?id=${id}`); const response = await apiConfig.get(`/mobile/portofolio?id=${id}&page=${page}`);
return response.data; return response.data;
} catch (error) { } catch (error) {

View File

@@ -41,16 +41,19 @@ export async function apiNotificationsSendById({
export async function apiGetNotificationsById({ export async function apiGetNotificationsById({
id, id,
category, category,
page = "1",
}: { }: {
id: string; id: string;
category: TypeNotificationCategoryApp; category: TypeNotificationCategoryApp;
page?: string;
}) { }) {
console.log("ID", id); console.log("ID", id);
console.log("Category", category); console.log("Category", category);
console.log("Page", page);
try { try {
const response = await apiConfig.get( const response = await apiConfig.get(
`/mobile/notification/${id}?category=${category}` `/mobile/notification/${id}?category=${category}&page=${page}`
); );
return response.data; return response.data;