diff --git a/app/(application)/(user)/event/(tabs)/contribution.tsx b/app/(application)/(user)/event/(tabs)/contribution.tsx index 8a5266b..af57eee 100644 --- a/app/(application)/(user)/event/(tabs)/contribution.tsx +++ b/app/(application)/(user)/event/(tabs)/contribution.tsx @@ -1,115 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - AvatarUsernameAndOtherComponent, - BoxWithHeaderSection, - LoaderCustom, - Spacing, - StackCustom, - TextCustom, - ViewWrapper -} from "@/components"; -import { useAuth } from "@/hooks/use-auth"; -import { - apiEventGetAll -} from "@/service/api-client/api-event"; -import { dateTimeView } from "@/utils/dateTimeView"; -import { useFocusEffect } from "expo-router"; -import _ from "lodash"; -import React, { useCallback, useState } from "react"; +import Event_ScreenContribution from "@/screens/Event/ScreenContribution"; export default function EventContribution() { - const { user } = useAuth(); - const [listData, setListData] = useState([]); - const [isLoadList, setIsLoadList] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, [user?.id]) - ); - - async function onLoadData() { - try { - setIsLoadList(true); - const response = await apiEventGetAll({ - category: "contribution", - userId: user?.id, - }); - console.log("[DATA] ", JSON.stringify(response.data, null, 2)); - if (response.success) { - setListData(response.data); - - // const responseListParticipants = await apiEventListOfParticipants({ - // id: response?.data?.Event?.id, - // }); - // console.log( - // "[LIST PARTICIPANTS]", - // JSON.stringify(responseListParticipants.data, null, 2) - // ); - // if (responseListParticipants.success) { - // setListParticipants(responseListParticipants.data); - // } - } - } catch (error) { - console.log("[ERROR]", error); - } finally { - setIsLoadList(false); - } - } - return ( - - {isLoadList ? ( - - ) : _.isEmpty(listData) ? ( - Belum ada kontribusi - ) : ( - listData.map((item: any, index: number) => ( - - - - {dateTimeView({ - date: item?.Event?.tanggal, - withoutTime: true, - })} - - } - /> - - - {item?.Event?.title} - - - - {/* - {item?.Event?.Event_Peserta?.map( - (item2: any, index2: number) => ( - - - - ) - )} - */} - - - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/event/(tabs)/index.tsx b/app/(application)/(user)/event/(tabs)/index.tsx index 1e8463f..30c47f0 100644 --- a/app/(application)/(user)/event/(tabs)/index.tsx +++ b/app/(application)/(user)/event/(tabs)/index.tsx @@ -1,63 +1,9 @@ -import { LoaderCustom, TextCustom } from "@/components"; -import ViewWrapper from "@/components/_ShareComponent/ViewWrapper"; -import FloatingButton from "@/components/Button/FloatingButton"; -import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection"; -import { apiEventGetAll } from "@/service/api-client/api-event"; -import { dateTimeView } from "@/utils/dateTimeView"; -import { router, useFocusEffect } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Event_ScreenBeranda from "@/screens/Event/ScreenBeranda"; export default function EventBeranda() { - const [listData, setListData] = useState([]); - const [isLoadData, setIsLoadData] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, []) - ); - - const onLoadData = async () => { - try { - setIsLoadData(true); - const response = await apiEventGetAll({category: "beranda"}); - // console.log("Response", JSON.stringify(response.data, null, 2)); - setListData(response.data); - } catch (error) { - console.log("[ERROR]", error); - } finally { - setIsLoadData(false); - } - }; - return ( - router.push("/event/create")} /> - } - > - {isLoadData ? ( - - ) : _.isEmpty(listData) ? ( - Belum ada event - ) : ( - listData.map((item: any, index) => ( - - - - {dateTimeView({ date: item?.tanggal, withoutTime: true })} - - } - /> - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/event/[id]/list-of-participants.tsx b/app/(application)/(user)/event/[id]/list-of-participants.tsx index 3987f4a..8eff075 100644 --- a/app/(application)/(user)/event/[id]/list-of-participants.tsx +++ b/app/(application)/(user)/event/[id]/list-of-participants.tsx @@ -1,110 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - AvatarUsernameAndOtherComponent, - BadgeCustom, - BaseBox, - LoaderCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import { - apiEventGetOne, - apiEventListOfParticipants, -} from "@/service/api-client/api-event"; -import dayjs, { Dayjs } from "dayjs"; -import { useFocusEffect, useLocalSearchParams } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; -import { View } from "react-native"; +import Event_ScreenListOfParticipants from "@/screens/Event/ScreenListOfParticipants"; export default function EventListOfParticipants() { - const { id } = useLocalSearchParams(); - const [startDate, setStartDate] = useState(); - const [listData, setListData] = useState(null); - const [loadtData, setLoadData] = useState(false); - - useFocusEffect( - useCallback(() => { - handlerLoadData(); - }, [id]) - ); - - const handlerLoadData = () => { - try { - onLoadData(); - onLoadList(); - } catch (error) { - console.log("[ERROR]", error); - } - }; - - const onLoadData = async () => { - try { - const response = await apiEventGetOne({ id: id as string }); - if (response.success) { - const date = dayjs(response.data.tanggal); - setStartDate(date); - } - } catch (error) { - console.log("[ERROR]", error); - } - }; - - const onLoadList = async () => { - try { - setLoadData(true); - const response = await apiEventListOfParticipants({ id: id as string }); - - if (response.success) { - setListData(response.data); - } - } catch (error) { - console.log("[ERROR]", error); - } finally { - setLoadData(false); - } - }; - return ( - - {loadtData && !listData ? ( - - ) : _.isEmpty(listData) ? ( - - Belum ada peserta - - ) : ( - listData?.map((item: any, index: number) => ( - - - - {item?.isPresent ? "Hadir" : "Tidak Hadir"} - - - ) : ( - - - - - ) - } - /> - - )) - )} - + <> + + ); } diff --git a/components/Notification/NotificationInitializer.tsx b/components/Notification/NotificationInitializer.tsx index 20ec2fd..deb415e 100644 --- a/components/Notification/NotificationInitializer.tsx +++ b/components/Notification/NotificationInitializer.tsx @@ -49,7 +49,7 @@ export default function NotificationInitializer() { const fcmToken = await getToken(messagingInstance); if (!fcmToken) { - console.warn("Tidak bisa mendapatkan FCM token"); + console.log("Tidak bisa mendapatkan FCM token"); // logout(); } diff --git a/components/_ShareComponent/NewWrapper.tsx b/components/_ShareComponent/NewWrapper.tsx index c59245e..2d7d56d 100644 --- a/components/_ShareComponent/NewWrapper.tsx +++ b/components/_ShareComponent/NewWrapper.tsx @@ -101,12 +101,14 @@ const NewWrapper = (props: NewWrapperProps) => { renderItem={listProps.renderItem} keyExtractor={ listProps.keyExtractor || - ((item) => { + ((item, index) => { if (item.id == null) { console.warn("Item tanpa 'id':", item); - return `fallback-${JSON.stringify(item)}`; + return `fallback-${index}-${JSON.stringify(item)}`; } - return String(item.id); + + // Gabungkan ID dengan indeks untuk mencegah duplikasi + return `${String(item.id)}-${index}`; }) } diff --git a/docs/prompt-for-qwen-code.md b/docs/prompt-for-qwen-code.md index 16ef577..b83d266 100644 --- a/docs/prompt-for-qwen-code.md +++ b/docs/prompt-for-qwen-code.md @@ -1,24 +1,24 @@ - + -File utama: screens/Event/ScreenHistory.tsx -Fun fecth: apiEventGetAll -File fetch: service/api-client/api-event.ts +File utama: screens/Event/ScreenListOfParticipants.tsx +Function fecth: apiEventListOfParticipants +File function fetch: service/api-client/api-event.ts File komponen wrapper: components/_ShareComponent/NewWrapper.tsx -File refrensi: screens/Job/MainViewStatus2.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" +Perbaiki fetch "Function fecth" , pada file "File function fetch" Jika tidak ada props page maka tambahkan props page dan default page: "1" -Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama - Gunakan bahasa indonesia pada cli agar saya mudah membacanya. - + + + Terapkan NewWrapper pada file: screens/Forum/DetailForum.tsx diff --git a/screens/Event/BoxPublishSection.tsx b/screens/Event/BoxPublishSection.tsx index 79cbc12..1e8a2ba 100644 --- a/screens/Event/BoxPublishSection.tsx +++ b/screens/Event/BoxPublishSection.tsx @@ -1,6 +1,7 @@ import { AvatarUsernameAndOtherComponent, BoxWithHeaderSection, + Spacing, StackCustom, TextCustom, } from "@/components"; diff --git a/screens/Event/ButtonStatusSection.tsx b/screens/Event/ButtonStatusSection.tsx index afc55d8..16ebe74 100644 --- a/screens/Event/ButtonStatusSection.tsx +++ b/screens/Event/ButtonStatusSection.tsx @@ -31,7 +31,7 @@ export default function Event_ButtonStatusSection({ type: "success", text1: response.message, }); - router.back(); + router.replace(`/event/(tabs)/status?status=draft`); } else { Toast.show({ type: "info", @@ -65,7 +65,7 @@ export default function Event_ButtonStatusSection({ type: "success", text1: response.message, }); - router.back(); + router.replace(`/event/(tabs)/status?status=review`); } else { Toast.show({ type: "info", @@ -99,7 +99,7 @@ export default function Event_ButtonStatusSection({ type: "success", text1: response.message, }); - router.back(); + router.replace(`/event/(tabs)/status?status=draft`); } else { Toast.show({ type: "info", diff --git a/screens/Event/ScreenBeranda.tsx b/screens/Event/ScreenBeranda.tsx new file mode 100644 index 0000000..469b23d --- /dev/null +++ b/screens/Event/ScreenBeranda.tsx @@ -0,0 +1,75 @@ +import { TextCustom } from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import FloatingButton from "@/components/Button/FloatingButton"; +import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { usePagination } from "@/hooks/use-pagination"; +import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection"; +import { apiEventGetAll } from "@/service/api-client/api-event"; +import { dateTimeView } from "@/utils/dateTimeView"; +import { router } from "expo-router"; +import { RefreshControl } from "react-native"; + + +export default function Event_ScreenBeranda() { + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiEventGetAll({ + category: "beranda", + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [], + onError: (error) => console.error("[ERROR] Fetch event beranda:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Belum ada event", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); + + // Render item event + const renderEventItem = ({ item }: { item: any }) => ( + + {dateTimeView({ date: item?.tanggal, withoutTime: true })} + + } + /> + ); + + + return ( + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + hideFooter + floatingButton={ + router.push("/event/create")} /> + } + /> + ); +} diff --git a/screens/Event/ScreenContribution.tsx b/screens/Event/ScreenContribution.tsx new file mode 100644 index 0000000..8f91f35 --- /dev/null +++ b/screens/Event/ScreenContribution.tsx @@ -0,0 +1,102 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + AvatarUsernameAndOtherComponent, + BoxWithHeaderSection, + Spacing, + StackCustom, + TextCustom +} from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { useAuth } from "@/hooks/use-auth"; +import { usePagination } from "@/hooks/use-pagination"; +import { apiEventGetAll } from "@/service/api-client/api-event"; +import { dateTimeView } from "@/utils/dateTimeView"; +import { RefreshControl } from "react-native"; + + +export default function Event_ScreenContribution() { + const { user } = useAuth(); + + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + if (!user?.id) return { data: [] }; + + return await apiEventGetAll({ + category: "contribution", + userId: user?.id, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [user?.id], + onError: (error) => + console.error("[ERROR] Fetch event contribution:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Belum ada kontribusi", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); + + // Render item event + const renderEventItem = ({ item }: { item: any }) => ( + + + + {dateTimeView({ + date: item?.Event?.tanggal, + withoutTime: true, + })} + + } + /> + + + {item?.Event?.title} + + + + + ); + + // useEffect(() => { + // pagination.onRefresh(); + // }, []); + + return ( + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + hideFooter + /> + ); +} diff --git a/screens/Event/ScreenHistory.tsx b/screens/Event/ScreenHistory.tsx index 0064762..7f30a93 100644 --- a/screens/Event/ScreenHistory.tsx +++ b/screens/Event/ScreenHistory.tsx @@ -2,6 +2,7 @@ import { ButtonCustom, Spacing, TextCustom } from "@/components"; import NewWrapper from "@/components/_ShareComponent/NewWrapper"; import { AccentColor, MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; import { createPaginationComponents } from "@/helpers/paginationHelpers"; import { useAuth } from "@/hooks/use-auth"; import { usePagination } from "@/hooks/use-pagination"; @@ -12,8 +13,6 @@ import _ from "lodash"; import { useState } from "react"; import { RefreshControl, View } from "react-native"; -const PAGE_SIZE = 5; - export default function Event_ScreenHistory() { const [activeCategory, setActiveCategory] = useState("all"); const { user } = useAuth(); @@ -27,25 +26,26 @@ export default function Event_ScreenHistory() { page: String(page), }); }, - pageSize: PAGE_SIZE, + pageSize: PAGINATION_DEFAULT_TAKE, dependencies: [user?.id, activeCategory], onError: (error) => console.error("[ERROR] Fetch event history:", error), }); // Generate komponen - const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({ - loading: pagination.loading, - refreshing: pagination.refreshing, - listData: pagination.listData, - emptyMessage: "Belum ada riwayat", - skeletonCount: 5, - skeletonHeight: 100, - }); + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Belum ada riwayat", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); // Render item event const renderEventItem = ({ item }: { item: any }) => ( diff --git a/screens/Event/ScreenListOfParticipants.tsx b/screens/Event/ScreenListOfParticipants.tsx new file mode 100644 index 0000000..85dbd23 --- /dev/null +++ b/screens/Event/ScreenListOfParticipants.tsx @@ -0,0 +1,121 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + AvatarUsernameAndOtherComponent, + BadgeCustom, + BaseBox +} from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; +import { createPaginationComponents } from "@/helpers/paginationHelpers"; +import { usePagination } from "@/hooks/use-pagination"; +import { + apiEventGetOne, + apiEventListOfParticipants, +} from "@/service/api-client/api-event"; +import dayjs, { Dayjs } from "dayjs"; +import { useLocalSearchParams } from "expo-router"; +import { useEffect, useState } from "react"; +import { RefreshControl, View } from "react-native"; + +export default function Event_ScreenListOfParticipants() { + const { id } = useLocalSearchParams(); + const [startDate, setStartDate] = useState(); + + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + return await apiEventListOfParticipants({ + id: id as string, + page: String(page), + }); + }, + pageSize: PAGINATION_DEFAULT_TAKE, + dependencies: [id], + onError: (error) => + console.error("[ERROR] Fetch event participants:", error), + }); + + useEffect(() => { + onLoadData(); + }, []); + + // Fetch event data separately (not part of pagination) + // useFocusEffect(() => { + // onLoadData(); + // pagination.onRefresh(); + // }); + + const onLoadData = async () => { + try { + const response = await apiEventGetOne({ id: id as string }); + if (response.success) { + const date = dayjs(response.data.tanggal); + setStartDate(date); + } + } catch (error) { + console.log("[ERROR]", error); + } + }; + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: "Belum ada peserta", + skeletonCount: PAGINATION_DEFAULT_TAKE, + skeletonHeight: 100, + }); + + // Render item participant + const renderParticipantItem = ({ item }: { item: any }) => ( + + + + {item?.isPresent ? "Hadir" : "Tidak Hadir"} + + + ) : ( + + - + + ) + } + /> + + ); + + return ( + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + /> + ); +} diff --git a/screens/Event/ScreenStatus.tsx b/screens/Event/ScreenStatus.tsx index 2a4637d..1971fb3 100644 --- a/screens/Event/ScreenStatus.tsx +++ b/screens/Event/ScreenStatus.tsx @@ -7,19 +7,16 @@ import { TextCustom, } from "@/components"; import NewWrapper from "@/components/_ShareComponent/NewWrapper"; -import { MainColor } from "@/constants/color-palet"; +import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value"; import { createPaginationComponents } from "@/helpers/paginationHelpers"; import { useAuth } from "@/hooks/use-auth"; import { usePagination } from "@/hooks/use-pagination"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { apiEventGetByStatus } from "@/service/api-client/api-event"; -import { useFocusEffect, useLocalSearchParams } from "expo-router"; -import _ from "lodash"; +import { useLocalSearchParams } from "expo-router"; import { useState } from "react"; import { RefreshControl, View } from "react-native"; -const PAGE_SIZE = 10; - export default function Event_ScreenStatus() { const { user } = useAuth(); const { status } = useLocalSearchParams<{ status?: string }>(); @@ -40,7 +37,7 @@ export default function Event_ScreenStatus() { page: String(page), }); }, - pageSize: PAGE_SIZE, + pageSize: PAGINATION_DEFAULT_TAKE, dependencies: [id, activeCategory], onError: (error) => console.error("[ERROR] Fetch event by status:", error), }); @@ -52,7 +49,7 @@ export default function Event_ScreenStatus() { refreshing: pagination.refreshing, listData: pagination.listData, emptyMessage: `Tidak ada data ${activeCategory}`, - skeletonCount: 5, + skeletonCount: PAGINATION_DEFAULT_TAKE, skeletonHeight: 100, }); diff --git a/screens/Forum/ViewBeranda3.tsx b/screens/Forum/ViewBeranda3.tsx index 1b5455f..2cb8471 100644 --- a/screens/Forum/ViewBeranda3.tsx +++ b/screens/Forum/ViewBeranda3.tsx @@ -2,7 +2,7 @@ import { AvatarComp, BackButton, FloatingButton, - SearchInput + SearchInput, } from "@/components"; import NewWrapper from "@/components/_ShareComponent/NewWrapper"; import { MainColor } from "@/constants/color-palet"; @@ -101,8 +101,9 @@ export default function Forum_ViewBeranda3() { /> + setSearch(text), 500)} diff --git a/service/api-client/api-event.ts b/service/api-client/api-event.ts index cacbf79..d30135e 100644 --- a/service/api-client/api-event.ts +++ b/service/api-client/api-event.ts @@ -117,9 +117,16 @@ export async function apiEventJoin({ } } -export async function apiEventListOfParticipants({ id }: { id?: string }) { +export async function apiEventListOfParticipants({ + id, + page = "1" +}: { + id?: string; + page?: string; +}) { try { - const response = await apiConfig.get(`/mobile/event/${id}/participants`); + const pageParam = page ? `?page=${page}` : ""; + const response = await apiConfig.get(`/mobile/event/${id}/participants${pageParam}`); return response.data; } catch (error) { throw error; diff --git a/styles/global-styles.ts b/styles/global-styles.ts index 1d6a538..0b3817b 100644 --- a/styles/global-styles.ts +++ b/styles/global-styles.ts @@ -17,7 +17,8 @@ export const GStyles = StyleSheet.create({ container: { flex: 1, paddingInline: PADDING_MEDIUM, - paddingBlock: PADDING_EXTRA_SMALL, + paddingTop: PADDING_EXTRA_SMALL, + // paddingBlock: PADDING_EXTRA_SMALL, backgroundColor: MainColor.darkblue, }, containerWithBackground: {