Penerapan notifikasi di event
Add: components/Button/BackButtonFromNotification.tsx types/type-collect-other.ts Fix: - android/app/build.gradle - app/(application)/(user)/_layout.tsx - app/(application)/(user)/event/(tabs)/_layout.tsx - app/(application)/(user)/event/(tabs)/status.tsx - app/(application)/(user)/event/create.tsx - app/(application)/(user)/job/(tabs)/_layout.tsx - app/(application)/(user)/notifications/index.tsx - app/(application)/admin/event/[id]/[status]/index.tsx - app/(application)/admin/event/[id]/reject-input.tsx - app/(application)/admin/notification/index.tsx - components/Notification/NotificationInitializer.tsx - hipmi-note.md - hooks/use-notification-store.tsx - screens/Admin/Event/funUpdateStatus.ts - service/api-notifications.ts - utils/formatChatTime.ts ### No Issue
This commit is contained in:
@@ -100,7 +100,7 @@ packagingOptions {
|
||||
applicationId 'com.bip.hipmimobileapp'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 3
|
||||
versionCode 4
|
||||
versionName "1.0.1"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
|
||||
@@ -53,7 +53,9 @@ export default function UserLayout() {
|
||||
/>
|
||||
|
||||
{/* ========== Notification Section ========= */}
|
||||
<Stack.Screen
|
||||
|
||||
{/* DIPINDAH DI FILE NOTIFICATION USER */}
|
||||
{/* <Stack.Screen
|
||||
name="notifications/index"
|
||||
options={{
|
||||
title: "Notifikasi",
|
||||
@@ -65,18 +67,21 @@ export default function UserLayout() {
|
||||
// />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{/* ========== Event Section ========= */}
|
||||
|
||||
<Stack.Screen
|
||||
name="event/(tabs)"
|
||||
options={{
|
||||
title: "Event",
|
||||
headerLeft: () => (
|
||||
<LeftButtonCustom path="/(application)/(user)/home" />
|
||||
),
|
||||
// NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
|
||||
// headerLeft: () => (
|
||||
// <LeftButtonCustom path="/(application)/(user)/home" />
|
||||
// ),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name="event/create"
|
||||
options={{
|
||||
@@ -520,7 +525,7 @@ export default function UserLayout() {
|
||||
options={{
|
||||
title: "Job Vacancy",
|
||||
// headerLeft: () => <BackButton path="/home" />,
|
||||
// Note: headerLeft di pindahkan ke Tabs Layout
|
||||
// NOTE: headerLeft di pindahkan ke Tabs Layout
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
|
||||
@@ -4,10 +4,34 @@ import {
|
||||
IconHome,
|
||||
IconStatus,
|
||||
} from "@/components/_Icon";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Tabs } from "expo-router";
|
||||
import { router, Tabs, useLocalSearchParams, useNavigation } from "expo-router";
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
export default function EventTabsLayout() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const { from, category } = useLocalSearchParams<{
|
||||
from?: string;
|
||||
category?: string;
|
||||
}>();
|
||||
|
||||
console.log("from", from);
|
||||
console.log("category", category);
|
||||
|
||||
// Atur header secara dinamis
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButtonFromNotification
|
||||
from={from as string}
|
||||
category={category as string}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
return (
|
||||
<Tabs screenOptions={TabsStyles}>
|
||||
<Tabs.Screen
|
||||
|
||||
@@ -11,15 +11,17 @@ 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 } from "expo-router";
|
||||
import { useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
export default function EventStatus() {
|
||||
const { user } = useAuth();
|
||||
const { status } = useLocalSearchParams<{ status?: string }>();
|
||||
|
||||
const id = user?.id || "";
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(
|
||||
"publish"
|
||||
status || "publish"
|
||||
);
|
||||
const [listData, setListData] = useState([]);
|
||||
const [loadingGetData, setLoadingGetData] = useState(false);
|
||||
@@ -73,7 +75,7 @@ export default function EventStatus() {
|
||||
listData.map((item: any, i) => (
|
||||
<BoxWithHeaderSection
|
||||
key={i}
|
||||
href={`/event/${item.id }/${activeCategory}/detail-event`}
|
||||
href={`/event/${item.id}/${activeCategory}/detail-event`}
|
||||
>
|
||||
<StackCustom gap={"xs"}>
|
||||
<Grid>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { apiEventCreate } from "@/service/api-client/api-event";
|
||||
import { apiMasterEventType } from "@/service/api-client/api-master";
|
||||
import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
|
||||
import { router } from "expo-router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
interface EventCreateProps {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { BackButton } from "@/components";
|
||||
import { IconHome, IconStatus } from "@/components/_Icon";
|
||||
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
|
||||
import { TabsStyles } from "@/styles/tabs-styles";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import {
|
||||
@@ -23,19 +24,7 @@ export default function JobTabsLayout() {
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<BackButton
|
||||
onPress={() => {
|
||||
if (from === "notifications") {
|
||||
router.replace(`/notifications?category=${category}`);
|
||||
} else {
|
||||
if (from) {
|
||||
router.replace(`/${from}` as any);
|
||||
} else {
|
||||
router.navigate("/home");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<BackButtonFromNotification from={from as string} category={category as string} />
|
||||
),
|
||||
});
|
||||
}, [from, router, navigation]);
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import {
|
||||
AlertDefaultSystem,
|
||||
BackButton,
|
||||
BaseBox,
|
||||
DrawerCustom,
|
||||
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 } from "@/constants/color-palet";
|
||||
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 { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { router, Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
import _ from "lodash";
|
||||
import { useCallback, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
@@ -39,8 +46,9 @@ const fixPath = ({
|
||||
|
||||
const separator = deepLink.includes("?") ? "&" : "?";
|
||||
|
||||
const fixedPath =
|
||||
`${deepLink}${separator}from=notifications&category=${_.lowerCase(categoryApp)}`;
|
||||
const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
|
||||
categoryApp
|
||||
)}`;
|
||||
|
||||
console.log("Fix Path", fixedPath);
|
||||
|
||||
@@ -103,6 +111,9 @@ export default function Notifications() {
|
||||
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);
|
||||
@@ -142,33 +153,96 @@ export default function Notifications() {
|
||||
};
|
||||
|
||||
return (
|
||||
<NewWrapper
|
||||
headerComponent={
|
||||
<ScrollableCustom
|
||||
data={listOfcategoriesAppNotification.map((e, i) => ({
|
||||
id: i,
|
||||
label: e.label,
|
||||
value: e.value,
|
||||
}))}
|
||||
onButtonPress={handlePress}
|
||||
activeId={activeCategory as string}
|
||||
<>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
title: "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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ export default function AdminEventDetail() {
|
||||
const response = await funUpdateStatusEvent({
|
||||
id: id as string,
|
||||
changeStatus: "publish",
|
||||
data: {catatan: "", senderId: user?.id as string}
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@/components";
|
||||
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
|
||||
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
|
||||
import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
|
||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||
@@ -14,9 +15,13 @@ import { useCallback, useState } from "react";
|
||||
import Toast from "react-native-toast-message";
|
||||
|
||||
export default function AdminEventRejectInput() {
|
||||
const { user } = useAuth();
|
||||
const { id, status } = useLocalSearchParams();
|
||||
|
||||
const [data, setData] = useState<any>("");
|
||||
const [data, setData] = useState<any>({
|
||||
catatan: "",
|
||||
senderId: "",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useFocusEffect(
|
||||
@@ -45,10 +50,16 @@ export default function AdminEventRejectInput() {
|
||||
}) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const newData = {
|
||||
catatan: data,
|
||||
senderId: user?.id as string,
|
||||
};
|
||||
|
||||
const response = await funUpdateStatusEvent({
|
||||
id: id as string,
|
||||
changeStatus,
|
||||
data: data,
|
||||
data: newData,
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
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";
|
||||
@@ -70,6 +76,9 @@ export default function AdminNotification() {
|
||||
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);
|
||||
@@ -89,7 +98,7 @@ export default function AdminNotification() {
|
||||
id: user?.id as any,
|
||||
category: activeCategory as any,
|
||||
});
|
||||
|
||||
|
||||
if (response.success) {
|
||||
setListData(response.data);
|
||||
} else {
|
||||
@@ -114,12 +123,12 @@ export default function AdminNotification() {
|
||||
options={{
|
||||
title: "Admin Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
// headerRight: () => (
|
||||
// <IconPlus
|
||||
// color={MainColor.yellow}
|
||||
// onPress={() => router.push("/test-notifications")}
|
||||
// />
|
||||
// ),
|
||||
headerRight: () => (
|
||||
<IconDot
|
||||
color={MainColor.yellow}
|
||||
onPress={() => setOpenDrawer(true)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -154,6 +163,51 @@ export default function AdminNotification() {
|
||||
))
|
||||
)}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
29
components/Button/BackButtonFromNotification.tsx
Normal file
29
components/Button/BackButtonFromNotification.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { BackButton } from "..";
|
||||
|
||||
export default function BackButtonFromNotification({
|
||||
from,
|
||||
category,
|
||||
}: {
|
||||
from: string;
|
||||
category?: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<>
|
||||
<BackButton
|
||||
onPress={() => {
|
||||
if (from === "notifications") {
|
||||
router.replace(`/notifications?category=${category}`);
|
||||
} else {
|
||||
if (from) {
|
||||
router.replace(`/${from}` as any);
|
||||
} else {
|
||||
router.navigate("/home");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -49,7 +49,8 @@ export default function NotificationInitializer() {
|
||||
|
||||
const fcmToken = await getToken(messagingInstance);
|
||||
if (!fcmToken) {
|
||||
logout();
|
||||
console.warn("Tidak bisa mendapatkan FCM token");
|
||||
// logout();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,3 +13,13 @@ Exp: open ios/HIPMIBADUNG.xcworkspace
|
||||
perubahan versi : npm version patch
|
||||
ios: bunx expo prebuild --platform ios
|
||||
android: bunx expo prebuild --platform android
|
||||
|
||||
### Android
|
||||
adb devices : cek device yang terhubung
|
||||
Note: izinkan perangkat dulu agar statusnya tidak unauthorized
|
||||
|
||||
adb install android/app/build/outputs/apk/debug/app-debug.apk : install apk ke device
|
||||
Note:
|
||||
Gunakan flag -s (serial) di perintah adb untuk menentukan target
|
||||
adb -s <0G52319V261040B2 ini adalah id nya> install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ type NotificationContextType = {
|
||||
notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
|
||||
) => void;
|
||||
markAsRead: (id: string) => void;
|
||||
markAsReadAll: (id: string) => void;
|
||||
syncUnreadCount: () => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -48,6 +49,7 @@ const NotificationContext = createContext<NotificationContextType>({
|
||||
unreadCount: 0,
|
||||
addNotification: () => {},
|
||||
markAsRead: () => {},
|
||||
markAsReadAll: () => {},
|
||||
syncUnreadCount: async () => {},
|
||||
});
|
||||
|
||||
@@ -98,7 +100,7 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
const markAsRead = async (id: string) => {
|
||||
try {
|
||||
const response = await apiNotificationMarkAsRead({ id });
|
||||
const response = await apiNotificationMarkAsRead({ id, category: "one" });
|
||||
console.log("🚀 Response Mark As Read:", response);
|
||||
|
||||
if (response.success) {
|
||||
@@ -114,6 +116,25 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const markAsReadAll = async (id: string) => {
|
||||
try {
|
||||
const response = await apiNotificationMarkAsRead({ id, category: "all" });
|
||||
console.log("🚀 Response Mark As Read All:", response);
|
||||
|
||||
if (response.success) {
|
||||
const cloneNotifications = [...notifications];
|
||||
const index = cloneNotifications.findIndex((n) => n?.data?.id === id);
|
||||
if (index !== -1) {
|
||||
cloneNotifications[index].isRead = true;
|
||||
setNotifications(cloneNotifications);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal mark as read:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const syncUnreadCount = async () => {
|
||||
try {
|
||||
const count = await apiNotificationUnreadCount({
|
||||
@@ -133,8 +154,9 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
|
||||
value={{
|
||||
notifications,
|
||||
addNotification,
|
||||
markAsRead,
|
||||
unreadCount,
|
||||
markAsRead,
|
||||
markAsReadAll,
|
||||
syncUnreadCount,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { apiAdminEventUpdateStatus } from "@/service/api-admin/api-admin-event";
|
||||
import { RejectedData } from "@/types/type-collect-other";
|
||||
|
||||
export const funUpdateStatusEvent = async ({
|
||||
id,
|
||||
@@ -7,17 +8,19 @@ export const funUpdateStatusEvent = async ({
|
||||
}: {
|
||||
id: string;
|
||||
changeStatus: "publish" | "review" | "reject";
|
||||
data?: string;
|
||||
data?: RejectedData;
|
||||
}) => {
|
||||
try {
|
||||
console.log("[DATA]", data);
|
||||
const response = await apiAdminEventUpdateStatus({
|
||||
id: id,
|
||||
changeStatus: changeStatus as any,
|
||||
data: data,
|
||||
data: data as any,
|
||||
});
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.log("[ERROR]", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
NotificationProp,
|
||||
TypeNotificationCategoryApp
|
||||
TypeNotificationCategoryApp,
|
||||
} from "@/types/type-notification-category";
|
||||
import { apiConfig } from "./api-config";
|
||||
|
||||
@@ -78,9 +78,22 @@ export async function apiNotificationUnreadCount({
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiNotificationMarkAsRead({ id }: { id: string }) {
|
||||
/**
|
||||
* @param id | notification id atau user id
|
||||
* @param category | "all" | "one" , jika "all" id yang harus di masukan adalah user id, jika "one" id yang harus di masukan adalah notification id
|
||||
* @type {string}
|
||||
*/
|
||||
export async function apiNotificationMarkAsRead({
|
||||
id,
|
||||
category,
|
||||
}: {
|
||||
id: string;
|
||||
category: "all" | "one";
|
||||
}) {
|
||||
try {
|
||||
const response = await apiConfig.put(`/mobile/notification/${id}`);
|
||||
const response = await apiConfig.put(
|
||||
`/mobile/notification/${id}?category=${category}`
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
4
types/type-collect-other.ts
Normal file
4
types/type-collect-other.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type RejectedData = {
|
||||
catatan?: string;
|
||||
senderId: string;
|
||||
};
|
||||
@@ -15,21 +15,21 @@ export const formatChatTime = (date: string | Date): string => {
|
||||
const messageDate = dayjs(date);
|
||||
const now = dayjs();
|
||||
|
||||
// Jika hari ini
|
||||
// Hari ini
|
||||
if (messageDate.isSame(now, 'day')) {
|
||||
return messageDate.format('HH:mm'); // contoh: "14.30"
|
||||
return messageDate.format('HH.mm'); // "14.30"
|
||||
}
|
||||
|
||||
// Jika kemarin
|
||||
// Kemarin
|
||||
if (messageDate.isSame(now.subtract(1, 'day'), 'day')) {
|
||||
return messageDate.format('dddd HH:mm');
|
||||
return `Kemarin, ${messageDate.format('HH.mm')}`; // "Kemarin, 14.30"
|
||||
}
|
||||
|
||||
// Jika dalam 7 hari terakhir (tapi bukan kemarin/ hari ini)
|
||||
// Dalam 7 hari terakhir (termasuk hari ini & kemarin sudah di-handle, jadi aman)
|
||||
if (now.diff(messageDate, 'day') < 7) {
|
||||
return messageDate.format('dddd HH:mm'); // contoh: "Senin 14:30"
|
||||
return `${messageDate.format('dddd')}, ${messageDate.format('HH.mm')}`; // "Senin, 13.00"
|
||||
}
|
||||
|
||||
// Lebih dari seminggu lalu → tampilkan tanggal
|
||||
return messageDate.format('D MMM YYYY HH:mm'); // contoh: "12 Mei 2024 14:30"
|
||||
};
|
||||
// Lebih dari 7 hari lalu
|
||||
return messageDate.format('DD - MM - YYYY, HH.mm'); // "05 - 11 - 2025, 14.00"
|
||||
};
|
||||
Reference in New Issue
Block a user