Fix Loaddata Voting

UI – Voting (User)
- app/(application)/(user)/voting/(tabs)/index.tsx
- app/(application)/(user)/voting/(tabs)/history.tsx
- app/(application)/(user)/voting/(tabs)/contribution.tsx
- app/(application)/(user)/voting/create.tsx
- app/(application)/(user)/voting/[id]/edit.tsx
- app/(application)/(user)/voting/[id]/[status]/detail.tsx
- app/(application)/(user)/voting/[id]/list-of-contributor.tsx

UI – Event
- app/(application)/(user)/event/[id]/edit.tsx
- screens/Event/ScreenStatus.tsx

UI – Voting Screens (New)
- screens/Voting/ScreenBeranda.tsx
- screens/Voting/ScreenContribution.tsx
- screens/Voting/ScreenHistory.tsx
- screens/Voting/ScreenListOfContributor.tsx
- screens/Voting/ScreenStatus.tsx
- screens/Voting/ButtonStatusSection.tsx

UI – Job
- screens/Job/ButtonStatusSection.tsx
- screens/Job/MainViewStatus2.tsx

API Client
- service/api-client/api-voting.ts

Docs
- docs/prompt-for-qwen-code.md

### No Issue
This commit is contained in:
2026-02-05 15:06:14 +08:00
parent d0abd14047
commit 72a3d42013
19 changed files with 451 additions and 330 deletions

View File

@@ -58,7 +58,7 @@ export default function EventEdit() {
try { try {
setIsLoadData(true); setIsLoadData(true);
const response = await apiEventGetOne({ id: id as string }); const response = await apiEventGetOne({ id: id as string });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
setSelectedDate(new Date(response.data.tanggal)); setSelectedDate(new Date(response.data.tanggal));

View File

@@ -1,59 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import Voting_ScreenContribution from "@/screens/Voting/ScreenContribution";
LoaderCustom,
TextCustom,
ViewWrapper
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState, useCallback } from "react";
export default function VotingContribution() { export default function VotingContribution() {
const { user } = useAuth(); return <Voting_ScreenContribution />;
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
category: "contribution",
authorId: user?.id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return (
<ViewWrapper hideFooter>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada kontribusi</TextCustom>
) : listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
data={item}
key={index}
href={`/voting/${item.id}/contribution`}
/>
))}
</ViewWrapper>
);
} }

View File

@@ -1,77 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { LoaderCustom, TextCustom, ViewWrapper } from "@/components"; import Voting_ScreenHistory from "@/screens/Voting/ScreenHistory";
import TabsTwoButtonCustom from "@/components/_ShareComponent/TabsTwoHeaderCustom";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { useAuth } from "@/hooks/use-auth";
import { useCallback, useState } from "react";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
export default function VotingHistory() { export default function VotingHistory() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadData();
}, [activeCategory])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
authorId: user?.id as string,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
const handlePress = (item: any) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
};
return ( return (
<ViewWrapper <>
hideFooter <Voting_ScreenHistory />
headerComponent={ </>
<TabsTwoButtonCustom
leftValue="all"
rightValue="main"
leftText="Semua Riwayat"
rightText="Riwayat Saya"
activeCategory={activeCategory}
handlePress={handlePress}
/>
}
>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada riwayat</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
key={index}
id={item.id}
data={item}
href={`/voting/${item.id}/history`}
/>
))
)}
</ViewWrapper>
); );
} }

View File

@@ -1,71 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import Voting_ScreenBeranda from "@/screens/Voting/ScreenBeranda";
FloatingButton,
LoaderCustom,
SearchInput,
TextCustom,
ViewWrapper,
} from "@/components";
import { useAuth } from "@/hooks/use-auth";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function VotingBeranda() { export default function VotingBeranda() {
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [loadingGetData, setLoadingGetData] = useState(false);
const [search, setSearch] = useState("");
useFocusEffect(
useCallback(() => {
onLoadData();
}, [search])
);
const onLoadData = async () => {
try {
setLoadingGetData(true);
const response = await apiVotingGetAll({
search,
category: "beranda",
userLoginId: user?.id,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setLoadingGetData(false);
}
};
return ( return (
<ViewWrapper <>
hideFooter <Voting_ScreenBeranda />
floatingButton={ </>
<FloatingButton onPress={() => router.push("/voting/create")} />
}
headerComponent={
<SearchInput placeholder="Cari voting" onChangeText={setSearch} />
}
>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Voting_BoxPublishSection
data={item}
key={index}
href={`/voting/${item.id}`}
/>
))
)}
</ViewWrapper>
); );
} }

View File

@@ -51,8 +51,6 @@ export default function VotingDetailStatus() {
setLoadingGetData(true); setLoadingGetData(true);
const response = await apiVotingGetOne({ id: id as string }); const response = await apiVotingGetOne({ id: id as string });
console.log("[DATA BY ID]", JSON.stringify(response, null, 2));
if (response.success) { if (response.success) {
setData(response.data); setData(response.data);
} }

View File

@@ -5,13 +5,14 @@ import {
ButtonCustom, ButtonCustom,
CenterCustom, CenterCustom,
LoaderCustom, LoaderCustom,
NewWrapper,
Spacing, Spacing,
StackCustom, StackCustom,
TextAreaCustom, TextAreaCustom,
TextCustom, TextCustom,
TextInputCustom, TextInputCustom
ViewWrapper,
} from "@/components"; } from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom"; import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
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";
@@ -34,7 +35,7 @@ interface IEditData {
Voting_DaftarNamaVote?: [ Voting_DaftarNamaVote?: [
{ {
value?: string; value?: string;
} },
]; ];
} }
@@ -47,7 +48,7 @@ export default function VotingEdit() {
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
onLoadData(); onLoadData();
}, [id]) }, [id]),
); );
const onLoadData = async () => { const onLoadData = async () => {
@@ -188,9 +189,9 @@ export default function VotingEdit() {
}; };
return ( return (
<ViewWrapper footerComponent={buttonSubmit()}> <NewWrapper footerComponent={buttonSubmit()}>
{loadingGetData ? ( {loadingGetData ? (
<LoaderCustom /> <ListSkeletonComponent />
) : ( ) : (
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<TextInputCustom <TextInputCustom
@@ -210,7 +211,7 @@ export default function VotingEdit() {
onChangeText={(text) => setData({ ...data, deskripsi: text })} onChangeText={(text) => setData({ ...data, deskripsi: text })}
/> />
<Spacing />
<DateTimePickerCustom <DateTimePickerCustom
minimumDate={new Date(Date.now())} minimumDate={new Date(Date.now())}
@@ -255,7 +256,7 @@ export default function VotingEdit() {
} }
</TextCustom> </TextCustom>
)} )}
<Spacing />
</StackCustom> </StackCustom>
{data?.Voting_DaftarNamaVote?.map((item: any, index: number) => ( {data?.Voting_DaftarNamaVote?.map((item: any, index: number) => (
@@ -270,7 +271,7 @@ export default function VotingEdit() {
...(data as any), ...(data as any),
Voting_DaftarNamaVote: data?.Voting_DaftarNamaVote?.map( Voting_DaftarNamaVote: data?.Voting_DaftarNamaVote?.map(
(item: any, i: any) => (item: any, i: any) =>
i === index ? { ...item, value } : item i === index ? { ...item, value } : item,
), ),
}) })
} }
@@ -327,6 +328,6 @@ export default function VotingEdit() {
<Spacing /> <Spacing />
</StackCustom> </StackCustom>
)} )}
</ViewWrapper> </NewWrapper>
); );
} }

View File

@@ -1,69 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import Voting_ScreenListOfContributor from "@/screens/Voting/ScreenListOfContributor";
AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox,
LoaderCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { apiVotingContribution } from "@/service/api-client/api-voting";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
export default function Voting_ListOfContributor() { export default function VotingListOfContributor() {
const { id } = useLocalSearchParams(); return <Voting_ScreenListOfContributor />;
const [listData, setListData] = useState<any>([]);
const [isLoadData, setIsLoadData] = useState(false);
useFocusEffect(
useCallback(() => {
onLoadList();
}, [id])
);
const onLoadList = async () => {
try {
setIsLoadData(true);
const response = await apiVotingContribution({
id: id as string,
authorId: "",
category: "list",
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadData(false);
}
};
return (
<ViewWrapper>
{isLoadData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada kontributor</TextCustom>
) : (
listData.map((item: any, index: number) => (
<BaseBox paddingTop={5} paddingBottom={5} key={index.toString()}>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId || ""}
name={item?.Author?.username || "Username"}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
rightComponent={
<BadgeCustom style={{ alignSelf: "flex-end" }}>
{item?.Voting_DaftarNamaVote?.value}
</BadgeCustom>
}
/>
</BaseBox>
))
)}
</ViewWrapper>
);
} }

View File

@@ -80,9 +80,7 @@ export default function VotingCreate() {
type: "success", type: "success",
text1: "Data berhasil disimpan", text1: "Data berhasil disimpan",
}); });
router.replace( router.replace("/voting/(tabs)/status?status=review");
"/(application)/(user)/voting/(tabs)/status?status=review",
);
} else { } else {
Toast.show({ Toast.show({
type: "error", type: "error",

View File

@@ -1,7 +1,7 @@
<!-- ===================== Start Penerapan Pagination ===================== --> <!-- ===================== Start Penerapan Pagination ===================== -->
File utama: screens/Voting/ScreenStatus.tsx File utama: screens/Voting/ScreenListOfContributor.tsx
Function fecth: apiVotingGetByStatus Function fecth: apiVotingContribution
File function fetch: service/api-client/api-voting.ts File function fetch: service/api-client/api-voting.ts
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
@@ -15,7 +15,8 @@ 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.
File refrensi: screens/Event/ScreenStatus.tsx <!-- Additional Prompt -->
File refrensi: app/(application)/(user)/event/[id]/list-of-participants.tsx
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama
<!-- ===================== End Penerapan Pagination ===================== --> <!-- ===================== End Penerapan Pagination ===================== -->

View File

@@ -13,8 +13,8 @@ import { useAuth } from "@/hooks/use-auth";
import { usePagination } from "@/hooks/use-pagination"; import { usePagination } from "@/hooks/use-pagination";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiEventGetByStatus } from "@/service/api-client/api-event"; import { apiEventGetByStatus } from "@/service/api-client/api-event";
import { useLocalSearchParams } from "expo-router"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native"; import { RefreshControl, View } from "react-native";
export default function Event_ScreenStatus() { export default function Event_ScreenStatus() {
@@ -84,6 +84,12 @@ export default function Event_ScreenStatus() {
pagination.reset(); pagination.reset();
}; };
useFocusEffect(
useCallback(() => {
pagination.onRefresh();
}, [activeCategory])
);
const tabsComponent = ( const tabsComponent = (
<ScrollableCustom <ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({ data={dummyMasterStatus.map((e, i) => ({

View File

@@ -20,6 +20,9 @@ export default function Job_ButtonStatusSection({
onSetLoading: (value: boolean) => void; onSetLoading: (value: boolean) => void;
isArchive?: boolean; isArchive?: boolean;
}) { }) {
const path : any =(status: string) => {
return `/job/(tabs)/status?status=${status}`
}
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
AlertDefaultSystem({ AlertDefaultSystem({
title: "Batalkan Review", title: "Batalkan Review",
@@ -39,7 +42,7 @@ export default function Job_ButtonStatusSection({
type: "success", type: "success",
text1: response.message, text1: response.message,
}); });
router.back(); router.replace(path("draft"));
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
@@ -76,14 +79,14 @@ export default function Job_ButtonStatusSection({
type: "success", type: "success",
text1: response.message, text1: response.message,
}); });
router.back(); router.replace(path("review"));
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
text1: "Info", text1: "Info",
text2: response.message, text2: response.message,
}); });
router.back(); router.back()
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
@@ -113,14 +116,14 @@ export default function Job_ButtonStatusSection({
type: "success", type: "success",
text1: response.message, text1: response.message,
}); });
router.back(); router.replace(path("draft"));
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
text1: "Info", text1: "Info",
text2: response.message, text2: response.message,
}); });
router.back(); router. back();
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);

View File

@@ -62,20 +62,18 @@ export default function Job_MainViewStatus2() {
</BaseBox> </BaseBox>
); );
// useFocusEffect(
// useCallback(() => {
// // Reset and load first page when category changes
// pagination.reset();
// // pagination.onRefresh();
// }, [activeCategory]),
// );
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
// Reset pagination saat kategori berubah // Reset pagination saat kategori berubah
pagination.reset(); pagination.reset();
}; };
useFocusEffect(
useCallback(() => {
pagination.onRefresh();
}, [activeCategory])
);
const scrollComponent = ( const scrollComponent = (
<ScrollableCustom <ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({ data={dummyMasterStatus.map((e, i) => ({

View File

@@ -3,7 +3,7 @@ import {
apiVotingDelete, apiVotingDelete,
apiVotingUpdateStatus, apiVotingUpdateStatus,
} from "@/service/api-client/api-voting"; } from "@/service/api-client/api-voting";
import { router } from "expo-router"; import { RelativePathString, router } from "expo-router";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function Voting_ButtonStatusSection({ export default function Voting_ButtonStatusSection({
@@ -17,6 +17,10 @@ export default function Voting_ButtonStatusSection({
isLoading: boolean; isLoading: boolean;
onSetLoading: (value: boolean) => void; onSetLoading: (value: boolean) => void;
}) { }) {
const path: any = (status: string) => {
return `/voting/(tabs)/status?status=${status}`;
};
const handleBatalkanReview = () => { const handleBatalkanReview = () => {
AlertDefaultSystem({ AlertDefaultSystem({
title: "Batalkan Review", title: "Batalkan Review",
@@ -34,9 +38,9 @@ export default function Voting_ButtonStatusSection({
if (response?.success) { if (response?.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: response.message, text1: "Berhasil batalkan review",
}); });
router.replace(`/voting/${id}/draft/detail`); router.replace(path("draft"));
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
@@ -71,9 +75,9 @@ export default function Voting_ButtonStatusSection({
if (response?.success) { if (response?.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: response.message, text1: "Berhasil ajukan review",
}); });
router.replace(`/voting/${id}/review/detail`); router.replace(path("review"));
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
@@ -108,9 +112,9 @@ export default function Voting_ButtonStatusSection({
if (response?.success) { if (response?.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: response.message, text1: "Berhasil edit kembali",
}); });
router.replace(`/voting/${id}/draft/detail`); router.replace(path("draft"));
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
@@ -135,31 +139,31 @@ export default function Voting_ButtonStatusSection({
textLeft: "Batal", textLeft: "Batal",
textRight: "Hapus", textRight: "Hapus",
onPressRight: async () => { onPressRight: async () => {
try { try {
onSetLoading(true); onSetLoading(true);
const response = await apiVotingDelete({ const response = await apiVotingDelete({
id: id as string, id: id as string,
}); });
if (response?.success) { if (response?.success) {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: response.message, text1: "Berhasil hapus data",
}); });
router.back(); router.back();
} else { } else {
Toast.show({ Toast.show({
type: "info", type: "info",
text1: "Info", text1: "Info",
text2: response.message, text2: response.message,
}); });
router.back(); router.back();
} }
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
} finally { } finally {
onSetLoading(false); onSetLoading(false);
} }
}, },
}); });
}; };

View File

@@ -0,0 +1,82 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { FloatingButton, SearchInput } from "@/components";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth";
import { usePagination } from "@/hooks/use-pagination";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { router } from "expo-router";
import { useMemo, useState } from "react";
import { RefreshControl } from "react-native";
export default function Voting_ScreenBeranda() {
const { user } = useAuth();
const [search, setSearch] = useState("");
const pagination = usePagination({
fetchFunction: async (page, searchQuery) => {
return await apiVotingGetAll({
search: searchQuery || "",
category: "beranda",
userLoginId: user?.id,
page: String(page),
});
},
pageSize: 5,
searchQuery: search,
dependencies: [user?.id],
onError: (error) => console.error("[ERROR] Fetch voting:", error),
});
// Gunakan helper untuk membuat komponen-komponen pagination
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
searchQuery: search,
emptyMessage: "Tidak ada data",
emptySearchMessage: "Tidak ada hasil pencarian",
skeletonCount: 5,
skeletonHeight: 200,
isInitialLoad: pagination.isInitialLoad,
});
// Render item untuk FlatList
const renderItem = useMemo(
() =>
({ item }: { item: any }) =>
(
<Voting_BoxPublishSection
data={item}
key={item.id}
href={`/voting/${item.id}`}
/>
),
[],
);
return (
<NewWrapper
listData={pagination.listData}
renderItem={renderItem}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onEndReached={pagination.loadMore}
refreshControl={
<RefreshControl
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
hideFooter
floatingButton={
<FloatingButton onPress={() => router.push("/voting/create")} />
}
headerComponent={
<SearchInput placeholder="Cari voting" onChangeText={setSearch} />
}
/>
);
}

View File

@@ -0,0 +1,70 @@
/* eslint-disable react-hooks/exhaustive-deps */
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth";
import { usePagination } from "@/hooks/use-pagination";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { useMemo } from "react";
import { RefreshControl } from "react-native";
export default function Voting_ScreenContribution() {
const { user } = useAuth();
const pagination = usePagination({
fetchFunction: async (page) => {
return await apiVotingGetAll({
category: "contribution",
authorId: user?.id as string,
page: String(page),
});
},
pageSize: 4,
dependencies: [user?.id],
onError: (error) => console.error("[ERROR] Fetch contribution:", error),
});
// Gunakan helper untuk membuat komponen-komponen pagination
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
emptyMessage: "Tidak ada kontribusi",
emptySearchMessage: "Tidak ada hasil pencarian",
skeletonCount: 5,
skeletonHeight: 200,
isInitialLoad: pagination.isInitialLoad,
});
// Render item untuk FlatList
const renderItem = useMemo(
() =>
({ item }: { item: any }) =>
(
<Voting_BoxPublishSection
data={item}
key={item.id}
href={`/voting/${item.id}/contribution`}
/>
),
[],
);
return (
<NewWrapper
listData={pagination.listData}
renderItem={renderItem}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onEndReached={pagination.loadMore}
refreshControl={
<RefreshControl
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
hideFooter
/>
);
}

View File

@@ -0,0 +1,114 @@
/* eslint-disable react-hooks/exhaustive-deps */
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";
import Voting_BoxPublishSection from "@/screens/Voting/BoxPublishSection";
import { apiVotingGetAll } from "@/service/api-client/api-voting";
import { useState } from "react";
import { RefreshControl, View } from "react-native";
export default function Voting_ScreenHistory() {
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const { user } = useAuth();
// Setup pagination
const pagination = usePagination({
fetchFunction: async (page) => {
return await apiVotingGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
authorId: user?.id as string,
page: String(page),
});
},
pageSize: PAGINATION_DEFAULT_TAKE,
dependencies: [user?.id, activeCategory],
onError: (error) => console.error("[ERROR] Fetch voting history:", error),
});
// Generate komponen
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 voting
const renderVotingItem = ({ item }: { item: any }) => (
<Voting_BoxPublishSection
key={item && item?.id}
data={item}
href={`/voting/${item.id}/history`}
/>
);
const handlePress = (item: any) => {
setActiveCategory(item);
// Reset pagination saat kategori berubah
pagination.reset();
};
const headerComponent = (
<View
style={{
flexDirection: "row",
alignItems: "center",
padding: 5,
backgroundColor: MainColor.soft_darkblue,
borderRadius: 50,
width: "100%",
}}
>
<ButtonCustom
backgroundColor={
activeCategory === "all" ? MainColor.yellow : AccentColor.blue
}
textColor={activeCategory === "all" ? MainColor.black : MainColor.white}
style={{ width: "49%" }}
onPress={() => handlePress("all")}
>
Semua Riwayat
</ButtonCustom>
<Spacing width={"2%"} />
<ButtonCustom
backgroundColor={
activeCategory === "main" ? MainColor.yellow : AccentColor.blue
}
textColor={
activeCategory === "main" ? MainColor.black : MainColor.white
}
style={{ width: "49%" }}
onPress={() => handlePress("main")}
>
Riwayat Saya
</ButtonCustom>
</View>
);
return (
<NewWrapper
headerComponent={headerComponent}
listData={pagination.listData}
renderItem={renderVotingItem}
refreshControl={
<RefreshControl
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
onEndReached={pagination.loadMore}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
hideFooter
/>
);
}

View File

@@ -0,0 +1,80 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AvatarUsernameAndOtherComponent,
BadgeCustom,
BaseBox,
Spacing,
TextCustom,
} from "@/components";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { usePagination } from "@/hooks/use-pagination";
import { apiVotingContribution } from "@/service/api-client/api-voting";
import { useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { RefreshControl, View } from "react-native";
export default function Voting_ScreenListOfContributor() {
const { id } = useLocalSearchParams();
// Setup pagination
const pagination = usePagination({
fetchFunction: async (page) => {
return await apiVotingContribution({
id: id as string,
authorId: "",
category: "list",
page: String(page),
});
},
pageSize: PAGINATION_DEFAULT_TAKE,
dependencies: [id],
onError: (error) =>
console.error("[ERROR] Fetch voting contributors:", error),
});
// Generate komponen
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
emptyMessage: "Tidak ada kontributor",
skeletonCount: PAGINATION_DEFAULT_TAKE,
skeletonHeight: 100,
});
// Render item contributor
const renderContributorItem = ({ item }: { item: any }) => (
<BaseBox paddingTop={5} paddingBottom={5} key={item?.id}>
<AvatarUsernameAndOtherComponent
avatar={item?.Author?.Profile?.imageId || ""}
name={item?.Author?.username || "Username"}
avatarHref={`/profile/${item?.Author?.Profile?.id}`}
rightComponent={
<BadgeCustom style={{ alignSelf: "flex-end" }}>
{item?.Voting_DaftarNamaVote?.value}
</BadgeCustom>
}
/>
</BaseBox>
);
return (
<NewWrapper
hideFooter
listData={pagination.listData}
renderItem={renderContributorItem}
refreshControl={
<RefreshControl
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
onEndReached={pagination.loadMore}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
/>
);
}

View File

@@ -8,17 +8,18 @@ import {
} from "@/components"; } from "@/components";
import NewWrapper from "@/components/_ShareComponent/NewWrapper"; import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { MainColor } from "@/constants/color-palet"; import { MainColor } from "@/constants/color-palet";
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers"; import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { usePagination } from "@/hooks/use-pagination"; import { usePagination } from "@/hooks/use-pagination";
import { dummyMasterStatus } from "@/lib/dummy-data/_master/status"; import { dummyMasterStatus } from "@/lib/dummy-data/_master/status";
import { apiVotingGetByStatus } from "@/service/api-client/api-voting"; import { apiVotingGetByStatus } from "@/service/api-client/api-voting";
import { dateTimeView } from "@/utils/dateTimeView"; import { dateTimeView } from "@/utils/dateTimeView";
import { useLocalSearchParams } from "expo-router"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import { useState } from "react"; import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native"; import { RefreshControl, View } from "react-native";
const PAGE_SIZE = 10; const PAGE_SIZE = 6
export default function Voting_ScreenStatus() { export default function Voting_ScreenStatus() {
const { user } = useAuth(); const { user } = useAuth();
@@ -40,7 +41,7 @@ export default function Voting_ScreenStatus() {
page: String(page), page: String(page),
}); });
}, },
pageSize: PAGE_SIZE, pageSize: PAGINATION_DEFAULT_TAKE,
dependencies: [id, activeCategory], dependencies: [id, activeCategory],
onError: (error) => console.error("[ERROR] Fetch voting by status:", error), onError: (error) => console.error("[ERROR] Fetch voting by status:", error),
}); });
@@ -91,6 +92,12 @@ export default function Voting_ScreenStatus() {
pagination.reset(); pagination.reset();
}; };
useFocusEffect(
useCallback(() => {
pagination.onRefresh();
}, [activeCategory])
);
const scrollComponent = ( const scrollComponent = (
<ScrollableCustom <ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({ data={dummyMasterStatus.map((e, i) => ({

View File

@@ -81,14 +81,15 @@ export async function apiVotingUpdateData({
} }
} }
export async function apiVotingGetAll({ search, category, authorId, userLoginId }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string, userLoginId?: string }) { export async function apiVotingGetAll({ search, category, authorId, userLoginId, page = "1" }: { search?: string, category: "beranda" | "contribution" | "all-history" | "my-history", authorId?: string, userLoginId?: string, page?: string }) {
try { try {
console.log("userLoginId", userLoginId); console.log("userLoginId", userLoginId);
const categoryQuery = category ? `?category=${category}` : ""; const categoryQuery = category ? `?category=${category}` : "";
const searchQuery = search ? `&search=${search}` : ""; const searchQuery = search ? `&search=${search}` : "";
const authorIdQuery = authorId ? `&authorId=${authorId}` : ""; const authorIdQuery = authorId ? `&authorId=${authorId}` : "";
const userLoginIdQuery = userLoginId ? `&userLoginId=${userLoginId}` : ""; const userLoginIdQuery = userLoginId ? `&userLoginId=${userLoginId}` : "";
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}${userLoginIdQuery}`); const pageQuery = `&page=${page}`;
const response = await apiConfig.get(`/mobile/voting${categoryQuery}${searchQuery}${authorIdQuery}${userLoginIdQuery}${pageQuery}`);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;
@@ -110,15 +111,17 @@ export async function apiVotingContribution({
id, id,
authorId, authorId,
category, category,
page = "1",
}: { }: {
id: string; id: string;
authorId: string; authorId: string;
category: "list" | "checked"; category: "list" | "checked";
page?: string;
}) { }) {
const query = const query =
category === "list" category === "list"
? "?category=list" ? `?category=list&page=${page}`
: `?category=checked&authorId=${authorId}`; : `?category=checked&authorId=${authorId}&page=${page}`;
try { try {
const response = await apiConfig.get( const response = await apiConfig.get(
`/mobile/voting/${id}/contribution${query}` `/mobile/voting/${id}/contribution${query}`