Fix load data event

Event – User
- app/(application)/(user)/event/(tabs)/history.tsx
- app/(application)/(user)/event/(tabs)/status.tsx
- app/(application)/(user)/event/[id]/edit.tsx
- app/(application)/(user)/event/create.tsx

Event – Screens (Untracked)
- screens/Event/ScreenHistory.tsx
- screens/Event/ScreenStatus.tsx

API
- service/api-client/api-event.ts

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

### No Issue
This commit is contained in:
2026-02-03 17:45:27 +08:00
parent 41f4a8ac99
commit 771ae45f26
8 changed files with 351 additions and 270 deletions

View File

@@ -1,104 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { ButtonCustom, LoaderCustom, Spacing, TextCustom } from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { useAuth } from "@/hooks/use-auth";
import Event_BoxPublishSection from "@/screens/Event/BoxPublishSection";
import { apiEventGetAll } from "@/service/api-client/api-event";
import { dateTimeView } from "@/utils/dateTimeView";
import _ from "lodash";
import { useEffect, useState } from "react";
import { View } from "react-native";
import Event_ScreenHistory from "@/screens/Event/ScreenHistory";
export default function EventHistory() {
const [activeCategory, setActiveCategory] = useState<string | null>("all");
const { user } = useAuth();
const [listData, setListData] = useState<any>([]);
const [isLoadList, setIsLoadList] = useState(false);
useEffect(() => {
onLoadData({ userId: user?.id });
}, [user?.id, activeCategory]);
async function onLoadData({ userId }: { userId?: string }) {
try {
setIsLoadList(true);
const response = await apiEventGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
userId: userId,
});
if (response.success) {
setListData(response.data);
}
} catch (error) {
console.log("[ERROR]", error);
} finally {
setIsLoadList(false);
}
}
const handlePress = (item: any) => {
setActiveCategory(item);
// tambahkan logika lain seperti filter dsb.
};
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 (
<ViewWrapper headerComponent={headerComponent} hideFooter>
{isLoadList ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Belum ada riwayat</TextCustom>
) : (
listData.map((item: any, index: number) => (
<Event_BoxPublishSection
key={index.toString()}
data={item}
rightComponentAvatar={
<TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
}
href={`/event/${item.id}/history`}
/>
))
)}
</ViewWrapper>
<>
<Event_ScreenHistory />
</>
);
}

View File

@@ -1,101 +1,10 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxWithHeaderSection,
Grid,
LoaderCustom,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { useAuth } from "@/hooks/use-auth";
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 { useCallback, useState } from "react";
import Event_ScreenStatus from "@/screens/Event/ScreenStatus";
export default function EventStatus() {
const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>(
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 apiEventGetByStatus({
id: id!,
status: activeCategory!,
});
// console.log("Response", JSON.stringify(response.data, null, 2));
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 tabsComponent = (
<ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<ViewWrapper headerComponent={tabsComponent}>
{loadingGetData ? (
<LoaderCustom />
) : _.isEmpty(listData) ? (
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom>
) : (
listData.map((item: any, i) => (
<BoxWithHeaderSection
key={i}
href={`/event/${item.id}/${activeCategory}/detail-event`}
>
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={8}>
<TextCustom truncate bold>
{item?.title}
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>
{new Date(item?.tanggal).toLocaleDateString()}
</TextCustom>
</Grid.Col>
</Grid>
<TextCustom truncate={2}>{item?.deskripsi}</TextCustom>
</StackCustom>
</BoxWithHeaderSection>
))
)}
</ViewWrapper>
<>
<Event_ScreenStatus />
</>
);
}

View File

@@ -1,7 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxButtonOnFooter,
ButtonCustom,
LoaderCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
@@ -10,6 +12,7 @@ import {
TextInputCustom,
ViewWrapper,
} from "@/components";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import {
apiEventGetOne,
@@ -48,7 +51,7 @@ export default function EventEdit() {
useFocusEffect(
useCallback(() => {
onLoadData();
}, [id])
}, [id]),
);
async function onLoadData() {
@@ -100,6 +103,15 @@ export default function EventEdit() {
const startDate = new Date(selectedDate as any);
const endDate = new Date(selectedEndDate as any);
if (!startDate) {
Toast.show({
type: "info",
text1: "Info",
text2: "Tanggal mulai tidak valid",
});
return false;
}
if (startDate >= endDate) {
Toast.show({
type: "info",
@@ -146,7 +158,7 @@ export default function EventEdit() {
const validateDateRange = (
selectedDate: string | Date,
selectedEndDate: string | Date
selectedEndDate: string | Date,
): { isValid: boolean; error?: string } => {
const startDate = new Date(selectedDate);
const endDate = new Date(selectedEndDate);
@@ -174,9 +186,19 @@ export default function EventEdit() {
return (
<>
<ViewWrapper>
<NewWrapper
footerComponent={
<BoxButtonOnFooter>
<ButtonCustom
isLoading={isLoading}
title="Update"
onPress={handlerSubmit}
/>
</BoxButtonOnFooter>
}
>
{isLoadData ? (
<LoaderCustom />
<ListSkeletonComponent />
) : (
<StackCustom gap={"xs"}>
<TextInputCustom
@@ -186,26 +208,15 @@ export default function EventEdit() {
value={data?.title}
onChangeText={(value) => setData({ ...data, title: value })}
/>
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value) => {
console.log(value);
setData({ ...data, eventMaster_TipeAcaraId: value });
}}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
showCount
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
<DateTimePickerCustom
minimumDate={new Date(Date.now())}
label="Tanggal & Waktu Mulai"
@@ -233,7 +244,7 @@ export default function EventEdit() {
{
validateDateRange(
selectedDate as any,
selectedEndDate as any
selectedEndDate as any,
).error
}
</TextCustom>
@@ -242,31 +253,37 @@ export default function EventEdit() {
{
validateDateRange(
selectedDate as any,
selectedEndDate as any
selectedEndDate as any,
).error
}
</TextCustom>
)}
<Spacing />
{/* <Spacing /> */}
</StackCustom>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
showCount
value={data?.deskripsi}
onChangeText={(value) => setData({ ...data, deskripsi: value })}
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || ""}
onChange={(value) => {
console.log(value);
setData({ ...data, eventMaster_TipeAcaraId: value });
}}
/>
<ButtonCustom
isLoading={isLoading}
title="Update"
onPress={handlerSubmit}
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
value={data?.lokasi}
onChangeText={(value) => setData({ ...data, lokasi: value })}
/>
</StackCustom>
)}
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -1,12 +1,13 @@
import {
BoxButtonOnFooter,
ButtonCustom,
NewWrapper,
SelectCustom,
Spacing,
StackCustom,
TextAreaCustom,
TextCustom,
TextInputCustom,
ViewWrapper,
} from "@/components";
import DateTimePickerCustom from "@/components/DateInput/DateTimePickerCustom";
import { useAuth } from "@/hooks/use-auth";
@@ -100,7 +101,6 @@ export default function EventCreate() {
setIsLoading(false);
}
};
const buttonSubmit = (
<ButtonCustom
@@ -112,7 +112,9 @@ export default function EventCreate() {
return (
<>
<ViewWrapper>
<NewWrapper
footerComponent={<BoxButtonOnFooter>{buttonSubmit}</BoxButtonOnFooter>}
>
<StackCustom gap={"xs"}>
<TextInputCustom
placeholder="Masukkan nama event"
@@ -121,24 +123,15 @@ export default function EventCreate() {
onChangeText={(value: any) => setData({ ...data, title: value })}
/>
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || null}
onChange={(value: any) =>
setData({ ...data, eventMaster_TipeAcaraId: value })
}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
showCount
value={data?.deskripsi || ""}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
/>
<DateTimePickerCustom
@@ -168,22 +161,28 @@ export default function EventCreate() {
</TextCustom>
)}
<Spacing />
<SelectCustom
label="Tipe Event"
placeholder="Pilih tipe event"
data={listTypeEvent.map((item: any) => ({
label: item.name,
value: item.id,
}))}
value={data?.eventMaster_TipeAcaraId || null}
onChange={(value: any) =>
setData({ ...data, eventMaster_TipeAcaraId: value })
}
/>
<TextInputCustom
label="Lokasi"
placeholder="Masukkan lokasi event"
required
onChangeText={(value: any) => setData({ ...data, lokasi: value })}
/>
</StackCustom>
<TextAreaCustom
label="Deskripsi"
placeholder="Masukkan deskripsi event"
required
showCount
value={data?.deskripsi || ""}
onChangeText={(value: any) =>
setData({ ...data, deskripsi: value })
}
/>
{buttonSubmit}
</StackCustom>
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -1,9 +1,10 @@
<!-- Start Penerapan Pagination -->
File utama: screens/Admin/Notification-Admin/ScreenNotificationAdmin2.tsx
Fun fecth: apiGetNotificationsById
File fetch: service/api-notifications.ts
File utama: screens/Event/ScreenHistory.tsx
Fun fecth: apiEventGetAll
File 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
@@ -13,6 +14,8 @@ Komponen pagination yang digunaka berada pada file hooks/use-pagination.tsx dan
Perbaiki fetch "Fun fecth" , pada file "File 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.
<!-- End Penerapan Pagination -->

View File

@@ -0,0 +1,121 @@
/* 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 { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth";
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 _ 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<string | null>("all");
const { user } = useAuth();
// Setup pagination
const pagination = usePagination({
fetchFunction: async (page) => {
return await apiEventGetAll({
category: activeCategory === "all" ? "all-history" : "my-history",
userId: user?.id,
page: String(page),
});
},
pageSize: PAGE_SIZE,
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,
});
// Render item event
const renderEventItem = ({ item }: { item: any }) => (
<Event_BoxPublishSection
key={item.id}
data={item}
rightComponentAvatar={
<TextCustom>
{dateTimeView({ date: item?.tanggal, withoutTime: true })}
</TextCustom>
}
href={`/event/${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={renderEventItem}
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,121 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BoxWithHeaderSection,
Grid,
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 { apiEventGetByStatus } from "@/service/api-client/api-event";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
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 }>();
const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>(
status || "publish"
);
// Setup pagination
const pagination = usePagination({
fetchFunction: async (page) => {
if (!id) return { data: [] };
return await apiEventGetByStatus({
id: id!,
status: activeCategory!,
page: String(page),
});
},
pageSize: PAGE_SIZE,
dependencies: [id, activeCategory],
onError: (error) => console.error("[ERROR] Fetch event 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 event
const renderEventItem = ({ item }: { item: any }) => (
<BoxWithHeaderSection
key={item.id}
href={`/event/${item.id}/${activeCategory}/detail-event`}
>
<StackCustom gap={"xs"}>
<Grid>
<Grid.Col span={8}>
<TextCustom truncate bold>
{item?.title}
</TextCustom>
</Grid.Col>
<Grid.Col span={4} style={{ alignItems: "flex-end" }}>
<TextCustom>
{new Date(item?.tanggal).toLocaleDateString()}
</TextCustom>
</Grid.Col>
</Grid>
<TextCustom truncate={2}>{item?.deskripsi}</TextCustom>
</StackCustom>
</BoxWithHeaderSection>
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// Reset pagination saat kategori berubah
pagination.reset();
};
const tabsComponent = (
<ScrollableCustom
data={dummyMasterStatus.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as any}
/>
);
return (
<NewWrapper
headerComponent={
<View style={{ paddingTop: 8 }}>
{tabsComponent}
</View>
}
listData={pagination.listData}
renderItem={renderEventItem}
refreshControl={
<RefreshControl
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
onEndReached={pagination.loadMore}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
/>
);
}

View File

@@ -14,12 +14,14 @@ export async function apiEventCreate(data: any) {
export async function apiEventGetByStatus({
id,
status,
page = "1",
}: {
id: string;
status: string;
page?: string;
}) {
try {
const response = await apiConfig.get(`/mobile/event/${id}/${status}`);
const response = await apiConfig.get(`/mobile/event/${id}/${status}?page=${page}`);
return response.data;
} catch (error) {
throw error;
@@ -79,15 +81,18 @@ export async function apiEventDelete({ id }: { id: string }) {
export async function apiEventGetAll({
category,
userId,
page = "1",
}: {
category?: "beranda" | "contribution" | "all-history" | "my-history";
userId?: string;
page?: string;
}) {
try {
const categoryEvent = category ? `?category=${category}` : "";
const userIdCreator = userId ? `&userId=${userId}` : "";
const pageParam = `&page=${page}`;
const response = await apiConfig.get(
`/mobile/event${categoryEvent}${userIdCreator}`
`/mobile/event${categoryEvent}${userIdCreator}${pageParam}`
);
return response.data;
} catch (error) {