Fitur notifikasi dan foreground

Add:
- types/type-notification-category.ts

Fix:
- app/(application)/(user)/notifications/index.tsx
- app/(application)/(user)/test-notifications.tsx
- app/(application)/admin/notification/index.tsx
- components/Notification/NotificationInitializer.tsx
- hooks/use-notification-store.tsx
- service/api-notifications.ts
- utils/formatChatTime.ts

### No Issue
This commit is contained in:
2025-12-24 15:29:58 +08:00
parent 54611ef812
commit 7743a2467c
8 changed files with 260 additions and 89 deletions

View File

@@ -1,79 +1,57 @@
import {
BaseBox,
Grid,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
ViewWrapper,
TextCustom
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { useState } from "react";
import { View } from "react-native";
const categories = [
{ value: "all", label: "Semua" },
{ value: "event", label: "Event" },
{ value: "job", label: "Job" },
{ value: "voting", label: "Voting" },
{ value: "donasi", label: "Donasi" },
{ value: "investasi", label: "Investasi" },
{ value: "forum", label: "Forum" },
{ value: "collaboration", label: "Collaboration" },
];
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import { AccentColor } from "@/constants/color-palet";
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 { router, useFocusEffect } from "expo-router";
import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native";
const selectedCategory = (value: string) => {
const category = categories.find((c) => c.value === value);
const category = listOfcategoriesAppNotification.find((c) => c.value === value);
return category?.label;
};
const BoxNotification = ({
index,
data,
activeCategory,
}: {
index: number;
data: any;
activeCategory: string | null;
}) => {
const { markAsRead } = useNotificationStore();
return (
<>
<BaseBox
onPress={() =>
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => {
console.log(
"Notification >",
selectedCategory(activeCategory as string)
)
}
);
router.push(data.deepLink);
markAsRead(data.id);
}}
>
<StackCustom>
<TextCustom bold>
# {selectedCategory(activeCategory as string)}
<TextCustom truncate={2} bold>
{data.title}
</TextCustom>
<View
style={{
borderBottomColor: MainColor.white_gray,
borderBottomWidth: 1,
}}
/>
<TextCustom truncate={2}>{data.pesan}</TextCustom>
<TextCustom truncate={2}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint odio
unde quidem voluptate quam culpa sequi molestias ipsa corrupti id,
soluta, nostrum adipisci similique, et illo asperiores deleniti eum
labore.
<TextCustom size="small" color="gray">
{formatChatTime(data.createdAt)}
</TextCustom>
<Grid>
<Grid.Col span={6}>
<TextCustom size="small" color="gray">
{index + 1} Agustus 2025
</TextCustom>
</Grid.Col>
<Grid.Col span={6} style={{ alignItems: "flex-end" }}>
<TextCustom size="small" color="gray">
Belum lihat
</TextCustom>
</Grid.Col>
</Grid>
</StackCustom>
</BaseBox>
</>
@@ -81,17 +59,54 @@ const BoxNotification = ({
};
export default function Notifications() {
const [activeCategory, setActiveCategory] = useState<string | null>("all");
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 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,
});
// console.log("Response Notification", JSON.stringify(response, null, 2));
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 (
<ViewWrapper
<NewWrapper
headerComponent={
<ScrollableCustom
data={categories.map((e, i) => ({
data={listOfcategoriesAppNotification.map((e, i) => ({
id: i,
label: e.label,
value: e.value,
@@ -100,12 +115,19 @@ export default function Notifications() {
activeId={activeCategory as string}
/>
}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
{Array.from({ length: 20 }).map((e, i) => (
<View key={i}>
<BoxNotification index={i} activeCategory={activeCategory as any} />
</View>
))}
</ViewWrapper>
{loading ? (
<ListSkeletonComponent/>
) : (
listData.map((e, i) => (
<View key={i}>
<BoxNotification data={e} activeCategory={activeCategory as any} />
</View>
))
)}
</NewWrapper>
);
}

View File

@@ -17,14 +17,14 @@ export default function TestNotification() {
console.log("[Data Dikirim]", data);
const response = await apiNotificationsSend({
data: {
title: "Test dari Backend (App Router)!",
title: "Test Notification !!",
body: data,
userLoginId: user?.id || "",
appId: "hipmi",
status: "publish",
kategoriApp: "EVENT",
kategoriApp: "JOB",
type: "announcement",
deepLink: "event/23189913801",
deepLink: "/job/cmhjz8u3h0005cfaxezyeilrr",
},
});

View File

@@ -1,7 +1,103 @@
import { BackButton, TextCustom, ViewWrapper } from "@/components";
import { Stack } from "expo-router";
import {
BackButton,
BaseBox,
NewWrapper,
ScrollableCustom,
StackCustom,
TextCustom,
} from "@/components";
import { AccentColor } from "@/constants/color-palet";
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 { router, Stack, useFocusEffect } from "expo-router";
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 AdminNotification() {
const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>("event");
const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const handlePress = (item: any) => {
setActiveCategory(item.value);
// tambahkan logika lain seperti filter dsb.
};
useFocusEffect(
useCallback(() => {
fecthData();
}, [activeCategory])
);
const fecthData = async () => {
try {
const response = await apiGetNotificationsById({
id: user?.id as any,
category: activeCategory as any,
});
// console.log("Response Notification", JSON.stringify(response, null, 2));
if (response.success) {
setListData(response.data);
} else {
setListData([]);
}
} catch (error) {
console.log("Error Notification", error);
}
};
const onRefresh = () => {
setRefreshing(true);
fecthData();
setRefreshing(false);
};
return (
<>
<Stack.Screen
@@ -12,9 +108,28 @@ export default function AdminNotification() {
}}
/>
<ViewWrapper>
<TextCustom>Notification</TextCustom>
</ViewWrapper>
<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} />
}
>
{listData.map((e, i) => (
<View key={i}>
<BoxNotification data={e} activeCategory={activeCategory as any} />
</View>
))}
</NewWrapper>
</>
);
}