From 60177a108719982012b492bc20c9b9da9b7c1fde Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Wed, 4 Feb 2026 12:00:00 +0800 Subject: [PATCH 1/3] Fix Component Datetime IOS Components - components/DateInput/DataTimeAndroid.tsx - components/DateInput/DateTimeIOS.tsx - components/Notification/NotificationInitializer.tsx Screens - screens/Event/ScreenStatus.tsx Docs - docs/prompt-for-qwen-code.md ### No Issue --- components/DateInput/DataTimeAndroid.tsx | 16 +- components/DateInput/DateTimeIOS.tsx | 147 +++++++++++++----- .../Notification/NotificationInitializer.tsx | 1 - docs/prompt-for-qwen-code.md | 3 +- screens/Event/ScreenStatus.tsx | 26 ++-- 5 files changed, 132 insertions(+), 61 deletions(-) diff --git a/components/DateInput/DataTimeAndroid.tsx b/components/DateInput/DataTimeAndroid.tsx index 66a2d5c..7fcd3fc 100644 --- a/components/DateInput/DataTimeAndroid.tsx +++ b/components/DateInput/DataTimeAndroid.tsx @@ -1,4 +1,3 @@ - // DateTimeInput.tsx import { MainColor } from "@/constants/color-palet"; import { GStyles } from "@/styles/global-styles"; @@ -7,7 +6,14 @@ import DateTimePicker, { DateTimePickerEvent, } from "@react-native-community/datetimepicker"; import React, { useCallback, useState } from "react"; -import { Pressable, StyleProp, Text, View, ViewStyle } from "react-native"; +import { + Keyboard, + Pressable, + StyleProp, + Text, + View, + ViewStyle, +} from "react-native"; import Grid from "../Grid/GridCustom"; import TextCustom from "../Text/TextCustom"; @@ -53,7 +59,7 @@ const DateTimeInput_Android: React.FC = ({ const [selectedDate, setSelectedDate] = useState(value as any); const [selectedTime, setSelectedTime] = useState(value as any); - console.log("Date Android", value) + console.log("Date Android", value); // Fungsi untuk menggabungkan tanggal dan waktu const combineDateAndTime = useCallback((date: Date, time: Date): Date => { @@ -62,7 +68,7 @@ const DateTimeInput_Android: React.FC = ({ time.getHours(), time.getMinutes(), time.getSeconds(), - time.getMilliseconds() + time.getMilliseconds(), ); return combined; }, []); @@ -92,10 +98,12 @@ const DateTimeInput_Android: React.FC = ({ }; const toggleDatePicker = () => { + Keyboard.dismiss(); setShowDate(!showDate); }; const toggleTimePicker = () => { + Keyboard.dismiss(); setShowTime(!showTime); }; diff --git a/components/DateInput/DateTimeIOS.tsx b/components/DateInput/DateTimeIOS.tsx index 54c06d7..de75675 100644 --- a/components/DateInput/DateTimeIOS.tsx +++ b/components/DateInput/DateTimeIOS.tsx @@ -1,13 +1,22 @@ // DateTimeInput.tsx -import { MainColor } from "@/constants/color-palet"; +import { AccentColor, MainColor } from "@/constants/color-palet"; import { GStyles } from "@/styles/global-styles"; import { Ionicons } from "@expo/vector-icons"; import DateTimePicker, { DateTimePickerEvent, } from "@react-native-community/datetimepicker"; import dayjs from "dayjs"; -import React, { useState } from "react"; -import { Button, StyleProp, Text, View, ViewStyle } from "react-native"; +import React, { useState, useRef } from "react"; +import { + Button, + StyleProp, + Text, + View, + ViewStyle, + Keyboard, + TouchableOpacity, + Modal, +} from "react-native"; import ClickableCustom from "../Clickable/ClickableCustom"; import TextCustom from "../Text/TextCustom"; @@ -50,20 +59,35 @@ const DateTimeInput_IOS: React.FC = ({ }) => { const [show, setShow] = useState(false); const [selectedDate, setSelectedDate] = useState( - value as any + value as any, + ); + // State sementara untuk menyimpan nilai yang dipilih + const [tempSelectedDate, setTempSelectedDate] = useState( + value as any, ); const handleConfirm = (event: any, date?: Date) => { if (event.type === "set" && date !== undefined) { - setSelectedDate(date); - onChange(date as any); + // Hanya perbarui state sementara, bukan state utama + setTempSelectedDate(date); } }; const handlePress = () => { + // Sembunyikan keyboard sebelum menampilkan date picker + Keyboard.dismiss(); + // Set state sementara ke nilai saat ini + setTempSelectedDate(selectedDate); setShow(!show); }; + // Fungsi untuk menangani klik di luar area picker + const handleOutsidePress = () => { + if (show) { + setShow(false); + } + }; + return ( <> = ({ ))} - {show && ( - <> - setShow(false)} + > + + + + + + true} // Mencegah event bubbling ke TouchableOpacity induk + onResponderRelease={() => {}} // Handler kosong untuk mencegah event bubbling > - {/* - { - setShow(false); - setSelectedDate(undefined); + {/* + + > + Pilih Tanggal & Waktu + + setShow(false)}> + + */} - - + { - setShow(false) - setSelectedDate(undefined) + setShow(false); + // Kembalikan ke nilai sebelumnya jika batal + setTempSelectedDate(selectedDate); }} style={{ + flex: 1, alignItems: "center", justifyContent: "center", padding: 12, borderRadius: 10, backgroundColor: MainColor.placeholder, - marginTop: 10, - width: "48%", }} > Batal - + - { - setShow(false) - onChange(selectedDate as any) + // Simpan nilai yang dipilih ke state utama hanya saat OK ditekan + setSelectedDate(tempSelectedDate); + onChange(tempSelectedDate as any); + setShow(false); }} style={{ + flex: 1, alignItems: "center", justifyContent: "center", padding: 12, borderRadius: 10, backgroundColor: MainColor.darkblue, - marginTop: 10, - width: "48%", }} > OK - + - - )} + + ); }; diff --git a/components/Notification/NotificationInitializer.tsx b/components/Notification/NotificationInitializer.tsx index f69e307..20ec2fd 100644 --- a/components/Notification/NotificationInitializer.tsx +++ b/components/Notification/NotificationInitializer.tsx @@ -51,7 +51,6 @@ export default function NotificationInitializer() { if (!fcmToken) { console.warn("Tidak bisa mendapatkan FCM token"); // logout(); - return; } console.log("✅ FCM Token:", fcmToken); diff --git a/docs/prompt-for-qwen-code.md b/docs/prompt-for-qwen-code.md index 7385ef8..16ef577 100644 --- a/docs/prompt-for-qwen-code.md +++ b/docs/prompt-for-qwen-code.md @@ -22,4 +22,5 @@ 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 \ No newline at end of file +Component yang digunakan: components/_ShareComponent/NewWrapper.tsx , karena ini adalah halaman detail saya ingin anda fokus pada props pada NewWrapper. Seperti + \ No newline at end of file diff --git a/screens/Event/ScreenStatus.tsx b/screens/Event/ScreenStatus.tsx index c8984d7..2a4637d 100644 --- a/screens/Event/ScreenStatus.tsx +++ b/screens/Event/ScreenStatus.tsx @@ -26,7 +26,7 @@ export default function Event_ScreenStatus() { const id = user?.id || ""; const [activeCategory, setActiveCategory] = useState( - status || "publish" + status || "publish", ); // Setup pagination @@ -46,14 +46,15 @@ export default function Event_ScreenStatus() { }); // Generate komponen - const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({ - loading: pagination.loading, - refreshing: pagination.refreshing, - listData: pagination.listData, - emptyMessage: `Tidak ada data ${activeCategory}`, - skeletonCount: 5, - skeletonHeight: 100, - }); + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: `Tidak ada data ${activeCategory}`, + skeletonCount: 5, + skeletonHeight: 100, + }); // Render item event const renderEventItem = ({ item }: { item: any }) => ( @@ -100,11 +101,8 @@ export default function Event_ScreenStatus() { return ( - {tabsComponent} - - } + hideFooter + headerComponent={{tabsComponent}} listData={pagination.listData} renderItem={renderEventItem} refreshControl={ From 5b2be20469dfb42c7dde333863146cb737e2c778 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Wed, 4 Feb 2026 16:56:48 +0800 Subject: [PATCH 2/3] Fix Loaddata pada event dan perbaikan tampilan pada NewWrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Event – User - app/(application)/(user)/event/(tabs)/contribution.tsx - app/(application)/(user)/event/(tabs)/index.tsx - app/(application)/(user)/event/[id]/list-of-participants.tsx Voting – User - app/(application)/(user)/voting/(tabs)/history.tsx Components - components/Notification/NotificationInitializer.tsx - components/_ShareComponent/NewWrapper.tsx Screens – Event - screens/Event/BoxPublishSection.tsx - screens/Event/ButtonStatusSection.tsx - screens/Event/ScreenHistory.tsx - screens/Event/ScreenStatus.tsx Screens – Forum - screens/Forum/ViewBeranda3.tsx API Client - service/api-client/api-event.ts Styles - styles/global-styles.ts Docs - docs/prompt-for-qwen-code.md Untracked (New Files) - screens/Event/ScreenBeranda.tsx - screens/Event/ScreenContribution.tsx - screens/Event/ScreenListOfParticipants.tsx #### No Issue --- .../(user)/event/(tabs)/contribution.tsx | 113 +--------------- .../(user)/event/(tabs)/index.tsx | 62 +-------- .../event/[id]/list-of-participants.tsx | 108 +--------------- .../Notification/NotificationInitializer.tsx | 2 +- components/_ShareComponent/NewWrapper.tsx | 8 +- docs/prompt-for-qwen-code.md | 18 +-- screens/Event/BoxPublishSection.tsx | 1 + screens/Event/ButtonStatusSection.tsx | 6 +- screens/Event/ScreenBeranda.tsx | 75 +++++++++++ screens/Event/ScreenContribution.tsx | 102 +++++++++++++++ screens/Event/ScreenHistory.tsx | 24 ++-- screens/Event/ScreenListOfParticipants.tsx | 121 ++++++++++++++++++ screens/Event/ScreenStatus.tsx | 11 +- screens/Forum/ViewBeranda3.tsx | 5 +- service/api-client/api-event.ts | 11 +- styles/global-styles.ts | 3 +- 16 files changed, 359 insertions(+), 311 deletions(-) create mode 100644 screens/Event/ScreenBeranda.tsx create mode 100644 screens/Event/ScreenContribution.tsx create mode 100644 screens/Event/ScreenListOfParticipants.tsx 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: { From d0abd14047a625f2c98162ecc2063f13da31b54a Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Wed, 4 Feb 2026 17:44:57 +0800 Subject: [PATCH 3/3] Fix Loaddata Voting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Voting – User - app/(application)/(user)/voting/(tabs)/status.tsx - app/(application)/(user)/voting/create.tsx Screens – Voting - screens/Voting/ButtonStatusSection.tsx API Client - service/api-client/api-voting.ts Global - app/+not-found.tsx - styles/global-styles.ts Docs - docs/prompt-for-qwen-code.md Untracked (New Files) - screens/Voting/ScreenStatus.tsx ### No issue --- .../(user)/voting/(tabs)/status.tsx | 104 +-------------- app/(application)/(user)/voting/create.tsx | 16 ++- app/+not-found.tsx | 14 +- docs/prompt-for-qwen-code.md | 10 +- screens/Voting/ButtonStatusSection.tsx | 6 +- screens/Voting/ScreenStatus.tsx | 125 ++++++++++++++++++ service/api-client/api-voting.ts | 4 +- styles/global-styles.ts | 1 + 8 files changed, 162 insertions(+), 118 deletions(-) create mode 100644 screens/Voting/ScreenStatus.tsx diff --git a/app/(application)/(user)/voting/(tabs)/status.tsx b/app/(application)/(user)/voting/(tabs)/status.tsx index 9760a80..ea49e2d 100644 --- a/app/(application)/(user)/voting/(tabs)/status.tsx +++ b/app/(application)/(user)/voting/(tabs)/status.tsx @@ -1,106 +1,10 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { - BadgeCustom, - BaseBox, - LoaderCustom, - ScrollableCustom, - StackCustom, - TextCustom, - ViewWrapper, -} from "@/components"; -import { useAuth } from "@/hooks/use-auth"; -import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; -import { apiVotingGetByStatus } from "@/service/api-client/api-voting"; -import { dateTimeView } from "@/utils/dateTimeView"; -import { useFocusEffect, useLocalSearchParams } from "expo-router"; -import _ from "lodash"; -import { useCallback, useState } from "react"; +import Voting_ScreenStatus from "@/screens/Voting/ScreenStatus"; export default function VotingStatus() { - const { user } = useAuth(); - const { status } = useLocalSearchParams<{ status?: string }>(); - - const id = user?.id || ""; - const [activeCategory, setActiveCategory] = useState( - status || "publish" - ); - - const [listData, setListData] = useState([]); - const [loadingGetData, setLoadingGetData] = useState(false); - - useFocusEffect( - useCallback(() => { - onLoadData(); - }, [activeCategory, id]) - ); - - async function onLoadData() { - try { - setLoadingGetData(true); - const response = await apiVotingGetByStatus({ - id: id as string, - status: activeCategory!, - }); - setListData(response.data); - } catch (error) { - console.log(error); - } finally { - setLoadingGetData(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 ( - - {loadingGetData ? ( - - ) : _.isEmpty(listData) ? ( - Tidak ada data {activeCategory} - ) : ( - listData.map((item: any, i: number) => ( - - - - {item?.title || ""} - - - {item?.awalVote && - dateTimeView({ - date: item?.awalVote, - withoutTime: true, - })}{" "} - -{" "} - {item?.akhirVote && - dateTimeView({ date: item?.akhirVote, withoutTime: true })} - - - - )) - )} - + <> + + ); } diff --git a/app/(application)/(user)/voting/create.tsx b/app/(application)/(user)/voting/create.tsx index 7e6d56a..1821425 100644 --- a/app/(application)/(user)/voting/create.tsx +++ b/app/(application)/(user)/voting/create.tsx @@ -3,11 +3,12 @@ import { BoxButtonOnFooter, ButtonCustom, CenterCustom, + NewWrapper, Spacing, StackCustom, TextAreaCustom, TextInputCustom, - ViewWrapper + ViewWrapper, } from "@/components"; import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom"; import { MainColor } from "@/constants/color-palet"; @@ -79,7 +80,9 @@ export default function VotingCreate() { type: "success", text1: "Data berhasil disimpan", }); - router.replace("/(application)/(user)/voting/(tabs)/status?status=review"); + router.replace( + "/(application)/(user)/voting/(tabs)/status?status=review", + ); } else { Toast.show({ type: "error", @@ -106,7 +109,7 @@ export default function VotingCreate() { }; return ( - + - {listVote.map((item, index) => ( setListVote( listVote.map((item, i) => - i === index ? { ...item, value } : item - ) + i === index ? { ...item, value } : item, + ), ) } /> @@ -198,6 +200,6 @@ export default function VotingCreate() { - + ); } diff --git a/app/+not-found.tsx b/app/+not-found.tsx index 20371a6..dc6c3a8 100644 --- a/app/+not-found.tsx +++ b/app/+not-found.tsx @@ -1,11 +1,21 @@ import { BackButton, StackCustom, TextCustom, ViewWrapper } from "@/components"; -import { Stack } from "expo-router"; +import { router, Stack } from "expo-router"; export default function NotFoundScreen() { + // Setelah (dengan penanganan): + const handleBack = () => { + if (router.canGoBack()) { + router.back(); + } else { + // Alternatif action ketika tidak bisa kembali + router.replace('/'); // atau navigasi ke halaman default + } + }; + return ( <> }} + options={{ headerShown: true, title: "", headerLeft: () => handleBack()} /> }} /> -File utama: screens/Event/ScreenListOfParticipants.tsx -Function fecth: apiEventListOfParticipants -File function fetch: service/api-client/api-event.ts +File utama: screens/Voting/ScreenStatus.tsx +Function fecth: apiVotingGetByStatus +File function fetch: service/api-client/api-voting.ts File komponen wrapper: components/_ShareComponent/NewWrapper.tsx Terapkan pagination pada file "File utama" @@ -15,8 +15,8 @@ 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 diff --git a/screens/Voting/ButtonStatusSection.tsx b/screens/Voting/ButtonStatusSection.tsx index 451d791..9de27c6 100644 --- a/screens/Voting/ButtonStatusSection.tsx +++ b/screens/Voting/ButtonStatusSection.tsx @@ -36,7 +36,7 @@ export default function Voting_ButtonStatusSection({ type: "success", text1: response.message, }); - router.back(); + router.replace(`/voting/${id}/draft/detail`); } else { Toast.show({ type: "info", @@ -73,7 +73,7 @@ export default function Voting_ButtonStatusSection({ type: "success", text1: response.message, }); - router.back(); + router.replace(`/voting/${id}/review/detail`); } else { Toast.show({ type: "info", @@ -110,7 +110,7 @@ export default function Voting_ButtonStatusSection({ type: "success", text1: response.message, }); - router.back(); + router.replace(`/voting/${id}/draft/detail`); } else { Toast.show({ type: "info", diff --git a/screens/Voting/ScreenStatus.tsx b/screens/Voting/ScreenStatus.tsx new file mode 100644 index 0000000..06c98e9 --- /dev/null +++ b/screens/Voting/ScreenStatus.tsx @@ -0,0 +1,125 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { + BadgeCustom, + BaseBox, + ScrollableCustom, + StackCustom, + TextCustom, +} from "@/components"; +import NewWrapper from "@/components/_ShareComponent/NewWrapper"; +import { MainColor } from "@/constants/color-palet"; +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 { apiVotingGetByStatus } from "@/service/api-client/api-voting"; +import { dateTimeView } from "@/utils/dateTimeView"; +import { useLocalSearchParams } from "expo-router"; +import { useState } from "react"; +import { RefreshControl, View } from "react-native"; + +const PAGE_SIZE = 10; + +export default function Voting_ScreenStatus() { + const { user } = useAuth(); + const { status } = useLocalSearchParams<{ status?: string }>(); + + const id = user?.id || ""; + const [activeCategory, setActiveCategory] = useState( + status || "publish", + ); + + // Setup pagination + const pagination = usePagination({ + fetchFunction: async (page) => { + if (!id) return { data: [] }; + + return await apiVotingGetByStatus({ + id: id as string, + status: activeCategory!, + page: String(page), + }); + }, + pageSize: PAGE_SIZE, + dependencies: [id, activeCategory], + onError: (error) => console.error("[ERROR] Fetch voting by status:", error), + }); + + // Generate komponen + const { ListEmptyComponent, ListFooterComponent } = + createPaginationComponents({ + loading: pagination.loading, + refreshing: pagination.refreshing, + listData: pagination.listData, + emptyMessage: `Tidak ada data ${activeCategory}`, + skeletonCount: 5, + skeletonHeight: 100, + }); + + // Render item voting + const renderVotingItem = ({ item }: { item: any }) => ( + + + + {item?.title || ""} + + + {item?.awalVote && + dateTimeView({ + date: item?.awalVote, + withoutTime: true, + })}{" "} + -{" "} + {item?.akhirVote && + dateTimeView({ date: item?.akhirVote, withoutTime: true })} + + + + ); + + const handlePress = (item: any) => { + setActiveCategory(item.value); + // Reset pagination saat kategori berubah + pagination.reset(); + }; + + const scrollComponent = ( + ({ + id: i, + label: e.label, + value: e.value, + }))} + onButtonPress={handlePress} + activeId={activeCategory as any} + /> + ); + + return ( + {scrollComponent}} + listData={pagination.listData} + renderItem={renderVotingItem} + refreshControl={ + + } + onEndReached={pagination.loadMore} + ListEmptyComponent={ListEmptyComponent} + ListFooterComponent={ListFooterComponent} + hideFooter + /> + ); +} diff --git a/service/api-client/api-voting.ts b/service/api-client/api-voting.ts index 159eea2..4cbe63e 100644 --- a/service/api-client/api-voting.ts +++ b/service/api-client/api-voting.ts @@ -14,12 +14,14 @@ export async function apiVotingCreate(data: any) { export async function apiVotingGetByStatus({ id, status, + page = "1", }: { id: string; status: string; + page?: string; }) { try { - const response = await apiConfig.get(`/mobile/voting/${id}/${status}`); + const response = await apiConfig.get(`/mobile/voting/${id}/${status}?page=${page}`); return response.data; } catch (error) { throw error; diff --git a/styles/global-styles.ts b/styles/global-styles.ts index 0b3817b..fed6179 100644 --- a/styles/global-styles.ts +++ b/styles/global-styles.ts @@ -18,6 +18,7 @@ export const GStyles = StyleSheet.create({ flex: 1, paddingInline: PADDING_MEDIUM, paddingTop: PADDING_EXTRA_SMALL, + paddingBottom: 5, // paddingBlock: PADDING_EXTRA_SMALL, backgroundColor: MainColor.darkblue, },