Fix load data notification

Notification – User
- app/(application)/(user)/notifications/index.tsx
- screens/Notification/ScreenNotification_V1.tsx
- screens/Notification/ScreenNotification_V2.tsx

Notification – Admin
- app/(application)/admin/notification/index.tsx
- screens/Admin/Notification-Admin/

Job
- screens/Job/MainViewStatus2.tsx

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

Deleted
- screens/Notification/ScreenNotification.tsx

### No Issue
This commit is contained in:
2026-02-03 16:59:09 +08:00
parent 48196cd46b
commit 41f4a8ac99
8 changed files with 840 additions and 270 deletions

View File

@@ -1,9 +1,11 @@
import ScreenNotification from "@/screens/Notification/ScreenNotification";
import ScreenNotification from "@/screens/Notification/ScreenNotification_V2";
import ScreenNotification_V1 from "@/screens/Notification/ScreenNotification_V1";
export default function Notification() {
return (
<>
<ScreenNotification />
{/* <ScreenNotification_V1 /> */}
</>
);
}

View File

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

View File

@@ -1,8 +1,8 @@
<!-- Start Penerapan Pagination -->
File utama: screens/Job/ScreenArchive2.tsx
Fun fecth: apiJobGetByStatus
File fetch: service/api-client/api-job.ts
File utama: screens/Admin/Notification-Admin/ScreenNotificationAdmin2.tsx
Fun fecth: apiGetNotificationsById
File fetch: service/api-notifications.ts
File komponen wrapper: components/_ShareComponent/NewWrapper.tsx
Terapkan pagination pada file "File utama"

View File

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

View File

@@ -0,0 +1,222 @@
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { usePagination } from "@/hooks/use-pagination";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState } from "react";
import { RefreshControl, View } from "react-native";
const PAGE_SIZE = 10;
const selectedCategory = (value: string) => {
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value,
);
return category?.label;
};
const BoxNotification = ({
data,
activeCategory,
setListData,
}: {
data: any;
activeCategory: string | null;
setListData: (data: any) => void;
}) => {
const { markAsRead } = useNotificationStore();
return (
<>
<BaseBox
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
console.log(
"Notification >",
selectedCategory(activeCategory as string),
);
router.push(data.deepLink);
markAsRead(data.id);
setListData((prev: any) =>
prev.map((item: any) =>
item.id === data.id ? { ...item, isRead: true } : item,
),
);
}}
>
<StackCustom>
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
</StackCustom>
</BaseBox>
</>
);
};
export default function Admin_ScreenNotification2() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>("event");
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
// Setup pagination
const pagination = usePagination({
fetchFunction: async (page) => {
if (!user?.id) return { data: [] };
return await apiGetNotificationsById({
id: user?.id as any,
category: activeCategory as any,
page: String(page),
});
},
pageSize: PAGE_SIZE,
dependencies: [user?.id, activeCategory],
onError: (error) =>
console.error("[ERROR] Fetch admin notifications:", error),
});
// Generate komponen
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
emptyMessage: "Belum ada notifikasi",
skeletonCount: 5,
skeletonHeight: 100,
});
// Render item notification
const renderNotificationItem = ({ item }: { item: any }) => (
<View key={item.id}>
<BoxNotification
data={item}
activeCategory={activeCategory as any}
setListData={pagination.setListData}
/>
</View>
);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// Reset pagination saat kategori berubah
pagination.reset();
};
return (
<>
<Stack.Screen
options={{
title: "Admin Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}}
/>
<NewWrapper
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
/>
}
listData={pagination.listData}
renderItem={renderNotificationItem}
refreshControl={
<RefreshControl
tintColor={MainColor.yellow}
colors={[MainColor.yellow]}
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
onEndReached={pagination.loadMore}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
/>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tandai Semua Dibaca",
value: "read-all",
icon: (
<Ionicons
name="reader-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
path: "",
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
message:
"Apakah Anda yakin ingin menandai semua notifikasi dibaca?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
const data = _.cloneDeep(pagination.listData);
data.forEach((e) => {
e.isRead = true;
});
pagination.setListData(data);
pagination.onRefresh();
setOpenDrawer(false);
},
});
}
}}
/>
</DrawerCustom>
</>
);
}

View File

@@ -1,22 +1,16 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
BaseBox,
ScrollableCustom,
TextCustom,
ViewWrapper,
} from "@/components";
import { BaseBox, ScrollableCustom, 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 { apiJobGetByStatus } from "@/service/api-client/api-job";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useState } from "react";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
export default function Job_MainViewStatus2() {
const { user } = useAuth();
@@ -24,7 +18,7 @@ export default function Job_MainViewStatus2() {
console.log("STATUS", status);
const [activeCategory, setActiveCategory] = useState<string | null>(
status || "publish"
status || "publish",
);
// Setup pagination
@@ -44,14 +38,15 @@ export default function Job_MainViewStatus2() {
});
// Generate komponen
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
emptyMessage: `Tidak ada data ${activeCategory}`,
skeletonCount: PAGINATION_DEFAULT_TAKE,
skeletonHeight: 100,
});
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
emptyMessage: `Tidak ada data ${activeCategory}`,
skeletonCount: PAGINATION_DEFAULT_TAKE,
skeletonHeight: 100,
});
// Render item job
const renderJobItem = ({ item }: { item: any }) => (
@@ -67,6 +62,14 @@ export default function Job_MainViewStatus2() {
</BaseBox>
);
// useFocusEffect(
// useCallback(() => {
// // Reset and load first page when category changes
// pagination.reset();
// // pagination.onRefresh();
// }, [activeCategory]),
// );
const handlePress = (item: any) => {
setActiveCategory(item.value);
// Reset pagination saat kategori berubah
@@ -87,11 +90,7 @@ export default function Job_MainViewStatus2() {
return (
<NewWrapper
headerComponent={
<View style={{ paddingTop: 8 }}>
{scrollComponent}
</View>
}
headerComponent={<View style={{ paddingTop: 8 }}>{scrollComponent}</View>}
listData={pagination.listData}
renderItem={renderJobItem}
refreshControl={

View File

@@ -0,0 +1,321 @@
/* eslint-disable react-hooks/exhaustive-deps */
import {
AlertDefaultSystem,
BackButton,
BaseBox,
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { Ionicons } from "@expo/vector-icons";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useEffect, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => {
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value,
);
return category?.label;
};
const fixPath = ({
deepLink,
categoryApp,
}: {
deepLink: string;
categoryApp: string;
}) => {
if (categoryApp === "OTHER") {
return deepLink;
}
const separator = deepLink.includes("?") ? "&" : "?";
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
categoryApp,
)}`;
console.log("Fix Path", fixedPath);
return fixedPath;
};
const BoxNotification = ({
data,
activeCategory,
setListData,
}: {
data: any;
activeCategory: string | null;
setListData: (data: any) => void;
}) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
return (
<>
<BaseBox
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
// console.log(
// "Notification >",
// selectedCategory(activeCategory as string)
// );
const newPath = fixPath({
deepLink: data.deepLink,
categoryApp: data.kategoriApp,
});
router.navigate(newPath as any);
selectedCategory(activeCategory as string);
if (!data.isRead) {
markAsRead(data.id);
const updatedData = {
...data,
isRead: true,
};
console.log("Updated Data", updatedData);
setListData((prev: any) =>
prev.map((item: any) =>
item.id === data.id ? updatedData : item,
),
);
}
}}
>
<StackCustom>
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
</StackCustom>
</BaseBox>
</>
);
};
export default function ScreenNotification_V1() {
const { user } = useAuth();
const { category } = useLocalSearchParams<{ category?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
category || "event",
);
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
const handlePress = (item: any) => {
setActiveCategory(item.value);
// Reset pagination saat kategori berubah
setListData([]);
setPage(1);
setHasMore(true);
};
// useFocusEffect(
// useCallback(() => {
// fecthData(1, true);
// }, [activeCategory])
// );
useEffect(() => {
fecthData(1, true);
}, [activeCategory]);
const fecthData = async (pageNum: number, clear: boolean = false) => {
if (pageNum === 1 && !clear) return; // Hindari duplicate call untuk page 1
try {
if (pageNum === 1) {
setLoading(true);
} else {
setLoadingMore(true);
}
const response = await apiGetNotificationsById({
id: user?.id as any,
category: activeCategory as any,
page: String(pageNum),
});
if (response.success) {
if (clear || pageNum === 1) {
setListData(response.data);
} else {
setListData((prev) => [...prev, ...response.data]);
}
// Jika data yang dikembalikan kurang dari ukuran halaman, maka tidak ada halaman berikutnya
setHasMore(response.data.length >= 10); // Asumsikan ukuran halaman 10
} else {
if (pageNum === 1) {
setListData([]);
}
}
} catch (error) {
console.log("Error Notification", error);
if (pageNum === 1) {
setListData([]);
}
} finally {
if (pageNum === 1) {
setLoading(false);
} else {
setLoadingMore(false);
}
}
};
const onRefresh = async () => {
setRefreshing(true);
await fecthData(1, true);
setRefreshing(false);
};
const loadMore = () => {
if (!loadingMore && hasMore) {
const nextPage = page + 1;
setPage(nextPage);
fecthData(nextPage, false);
}
};
return (
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}}
/>
<NewWrapper
hideFooter
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
}))}
onButtonPress={handlePress}
activeId={activeCategory as string}
/>
}
listData={listData}
renderItem={({ item }) => (
<View key={item.id}>
<BoxNotification
data={item}
activeCategory={activeCategory as any}
setListData={setListData}
/>
</View>
)}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
onEndReached={loadMore}
// onEndReachedThreshold={0.1}
ListEmptyComponent={
loading ? (
<ListSkeletonComponent />
) : (
<NoDataText text="Belum ada notifikasi" />
)
}
ListFooterComponent={
loadingMore ? (
<View style={{ padding: 16, alignItems: "center" }}>
<LoaderCustom />
</View>
) : null
}
/>
<DrawerCustom
isVisible={openDrawer}
closeDrawer={() => setOpenDrawer(false)}
height={"auto"}
>
<MenuDrawerDynamicGrid
data={[
{
label: "Tandai Semua Dibaca",
value: "read-all",
icon: (
<Ionicons
name="reader-outline"
size={ICON_SIZE_SMALL}
color={MainColor.white}
/>
),
path: "",
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
message:
"Apakah Anda yakin ingin menandai semua notifikasi dibaca?",
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
const data = _.cloneDeep(listData);
data.forEach((e) => {
e.isRead = true;
});
setListData(data);
onRefresh();
setOpenDrawer(false);
},
});
}
}}
/>
</DrawerCustom>
</>
);
}

View File

@@ -11,26 +11,32 @@ import {
TextCustom,
} from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet";
import { ICON_SIZE_SMALL, PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
import {
ICON_SIZE_SMALL,
PAGINATION_DEFAULT_TAKE,
} from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store";
import { usePagination } from "@/hooks/use-pagination";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
import {
router,
Stack,
useFocusEffect,
useLocalSearchParams,
} from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => {
const category = listOfcategoriesAppNotification.find(
(c) => c.value === value
(c) => c.value === value,
);
return category?.label;
};
@@ -49,7 +55,7 @@ const fixPath = ({
const separator = deepLink.includes("?") ? "&" : "?";
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
categoryApp
categoryApp,
)}`;
console.log("Fix Path", fixedPath);
@@ -60,21 +66,18 @@ const fixPath = ({
const BoxNotification = ({
data,
activeCategory,
setListData,
}: {
data: any;
activeCategory: string | null;
setListData: (data: any) => void;
}) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore();
return (
<>
<BaseBox
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
// console.log(
// "Notification >",
// selectedCategory(activeCategory as string)
// );
const newPath = fixPath({
deepLink: data.deepLink,
categoryApp: data.kategoriApp,
@@ -85,6 +88,11 @@ const BoxNotification = ({
if (!data.isRead) {
markAsRead(data.id);
setListData((prev: any) =>
prev.map((item: any) =>
item.id === data.id ? { ...item, isRead: true } : item,
),
);
}
}}
>
@@ -108,7 +116,7 @@ export default function ScreenNotification() {
const { user } = useAuth();
const { category } = useLocalSearchParams<{ category?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
category || "event"
category || "event",
);
const [openDrawer, setOpenDrawer] = useState(false);
@@ -117,6 +125,8 @@ export default function ScreenNotification() {
// Initialize pagination for notifications
const pagination = usePagination({
fetchFunction: async (page) => {
if (!user?.id) return { data: [] };
return await apiGetNotificationsById({
id: user?.id as string,
category: activeCategory as any,
@@ -127,20 +137,19 @@ export default function ScreenNotification() {
dependencies: [activeCategory],
});
useFocusEffect(
useCallback(() => {
// Reset and load first page when category changes
pagination.reset();
pagination.onRefresh();
}, [activeCategory])
);
// useFocusEffect(
// useCallback(() => {
// // Reset and load first page when category changes
// pagination.reset();
// pagination.onRefresh();
// }, [activeCategory]),
// );
const handlePress = (item: any) => {
console.log("ITEM", item.value);
setActiveCategory(item.value);
// Reset and load first page when category changes
pagination.reset();
pagination.onRefresh();
// pagination.reset();
// pagination.onRefresh();
};
// Render individual notification item
@@ -149,24 +158,26 @@ export default function ScreenNotification() {
<BoxNotification
data={item}
activeCategory={activeCategory as any}
setListData={pagination.setListData}
/>
</View>
);
// Generate pagination components using helper
const { ListEmptyComponent, ListFooterComponent } = createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
isInitialLoad: pagination.isInitialLoad,
emptyMessage: "Belum ada notifikasi",
skeletonCount: 5,
skeletonHeight: 100,
});
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
isInitialLoad: pagination.isInitialLoad,
emptyMessage: "Belum ada notifikasi",
skeletonCount: 5,
skeletonHeight: 100,
});
return (
<>
<Stack.Screen
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
@@ -180,6 +191,7 @@ export default function ScreenNotification() {
/>
<NewWrapper
hideFooter
headerComponent={
<ScrollableCustom
data={listOfcategoriesAppNotification.map((e, i) => ({
@@ -225,7 +237,7 @@ export default function ScreenNotification() {
},
]}
onPressItem={(item: any) => {
console.log("Item", item.value);
// console.log("Item", item.value);
if (item.value === "read-all") {
AlertDefaultSystem({
title: "Tandai Semua Dibaca",
@@ -234,9 +246,13 @@ export default function ScreenNotification() {
textLeft: "Batal",
textRight: "Ya",
onPressRight: () => {
markAsReadAll(user?.id as any);
// Reset and refresh data after marking all as read
pagination.reset();
markAsReadAll(user?.id as any);
const data = _.cloneDeep(pagination.listData);
data.forEach((e) => {
e.isRead = true;
});
pagination.setListData(data);
pagination.onRefresh();
setOpenDrawer(false);
},