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:
2026-01-13 17:41:30 +08:00
parent ca33dd83bb
commit 6e2046467f
18 changed files with 325 additions and 83 deletions

View File

@@ -100,7 +100,7 @@ packagingOptions {
applicationId 'com.bip.hipmimobileapp' applicationId 'com.bip.hipmimobileapp'
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 3 versionCode 4
versionName "1.0.1" versionName "1.0.1"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""

View File

@@ -53,7 +53,9 @@ export default function UserLayout() {
/> />
{/* ========== Notification Section ========= */} {/* ========== Notification Section ========= */}
<Stack.Screen
{/* DIPINDAH DI FILE NOTIFICATION USER */}
{/* <Stack.Screen
name="notifications/index" name="notifications/index"
options={{ options={{
title: "Notifikasi", title: "Notifikasi",
@@ -65,18 +67,21 @@ export default function UserLayout() {
// /> // />
// ), // ),
}} }}
/> /> */}
{/* ========== Event Section ========= */} {/* ========== Event Section ========= */}
<Stack.Screen <Stack.Screen
name="event/(tabs)" name="event/(tabs)"
options={{ options={{
title: "Event", title: "Event",
headerLeft: () => ( // NOTE: DIPINDAH DI FILE /Event/(Tabs)/_layout.tsx
<LeftButtonCustom path="/(application)/(user)/home" /> // headerLeft: () => (
), // <LeftButtonCustom path="/(application)/(user)/home" />
// ),
}} }}
/> />
<Stack.Screen <Stack.Screen
name="event/create" name="event/create"
options={{ options={{
@@ -520,7 +525,7 @@ export default function UserLayout() {
options={{ options={{
title: "Job Vacancy", title: "Job Vacancy",
// headerLeft: () => <BackButton path="/home" />, // headerLeft: () => <BackButton path="/home" />,
// Note: headerLeft di pindahkan ke Tabs Layout // NOTE: headerLeft di pindahkan ke Tabs Layout
}} }}
/> />
<Stack.Screen <Stack.Screen

View File

@@ -4,10 +4,34 @@ import {
IconHome, IconHome,
IconStatus, IconStatus,
} from "@/components/_Icon"; } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles"; 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() { 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 ( return (
<Tabs screenOptions={TabsStyles}> <Tabs screenOptions={TabsStyles}>
<Tabs.Screen <Tabs.Screen

View File

@@ -11,15 +11,17 @@ import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
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 { useFocusEffect } from "expo-router"; import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
export default function EventStatus() { export default function EventStatus() {
const { user } = useAuth(); const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
const id = user?.id || ""; const id = user?.id || "";
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" status || "publish"
); );
const [listData, setListData] = useState([]); const [listData, setListData] = useState([]);
const [loadingGetData, setLoadingGetData] = useState(false); const [loadingGetData, setLoadingGetData] = useState(false);
@@ -73,7 +75,7 @@ export default function EventStatus() {
listData.map((item: any, i) => ( listData.map((item: any, i) => (
<BoxWithHeaderSection <BoxWithHeaderSection
key={i} key={i}
href={`/event/${item.id }/${activeCategory}/detail-event`} href={`/event/${item.id}/${activeCategory}/detail-event`}
> >
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
<Grid> <Grid>

View File

@@ -14,7 +14,7 @@ import { apiEventCreate } from "@/service/api-client/api-event";
import { apiMasterEventType } from "@/service/api-client/api-master"; import { apiMasterEventType } from "@/service/api-client/api-master";
import { DateTimePickerEvent } from "@react-native-community/datetimepicker"; import { DateTimePickerEvent } from "@react-native-community/datetimepicker";
import { router } from "expo-router"; import { router } from "expo-router";
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
interface EventCreateProps { interface EventCreateProps {

View File

@@ -1,6 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { BackButton } from "@/components"; import { BackButton } from "@/components";
import { IconHome, IconStatus } from "@/components/_Icon"; import { IconHome, IconStatus } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles"; import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons"; import { Ionicons } from "@expo/vector-icons";
import { import {
@@ -23,19 +24,7 @@ export default function JobTabsLayout() {
useLayoutEffect(() => { useLayoutEffect(() => {
navigation.setOptions({ navigation.setOptions({
headerLeft: () => ( headerLeft: () => (
<BackButton <BackButtonFromNotification from={from as string} category={category as string} />
onPress={() => {
if (from === "notifications") {
router.replace(`/notifications?category=${category}`);
} else {
if (from) {
router.replace(`/${from}` as any);
} else {
router.navigate("/home");
}
}
}}
/>
), ),
}); });
}, [from, router, navigation]); }, [from, router, navigation]);

View File

@@ -1,20 +1,27 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import { import {
AlertDefaultSystem,
BackButton,
BaseBox, BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper, NewWrapper,
ScrollableCustom, ScrollableCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
} from "@/components"; } from "@/components";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent"; import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText"; 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 { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store"; import { useNotificationStore } from "@/hooks/use-notification-store";
import { apiGetNotificationsById } from "@/service/api-notifications"; import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category"; import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime"; 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 _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { RefreshControl, View } from "react-native"; import { RefreshControl, View } from "react-native";
@@ -39,8 +46,9 @@ const fixPath = ({
const separator = deepLink.includes("?") ? "&" : "?"; const separator = deepLink.includes("?") ? "&" : "?";
const fixedPath = const fixedPath = `${deepLink}${separator}from=notifications&category=${_.lowerCase(
`${deepLink}${separator}from=notifications&category=${_.lowerCase(categoryApp)}`; categoryApp
)}`;
console.log("Fix Path", fixedPath); console.log("Fix Path", fixedPath);
@@ -103,6 +111,9 @@ export default function Notifications() {
const [listData, setListData] = useState<any[]>([]); const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
@@ -142,6 +153,20 @@ export default function Notifications() {
}; };
return ( return (
<>
<Stack.Screen
options={{
title: "Notifikasi",
headerLeft: () => <BackButton />,
headerRight: () => (
<IconDot
color={MainColor.yellow}
onPress={() => setOpenDrawer(true)}
/>
),
}}
/>
<NewWrapper <NewWrapper
headerComponent={ headerComponent={
<ScrollableCustom <ScrollableCustom
@@ -165,10 +190,59 @@ export default function Notifications() {
) : ( ) : (
listData.map((e, i) => ( listData.map((e, i) => (
<View key={i}> <View key={i}>
<BoxNotification data={e} activeCategory={activeCategory as any} /> <BoxNotification
data={e}
activeCategory={activeCategory as any}
/>
</View> </View>
)) ))
)} )}
</NewWrapper> </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

@@ -126,6 +126,7 @@ export default function AdminEventDetail() {
const response = await funUpdateStatusEvent({ const response = await funUpdateStatusEvent({
id: id as string, id: id as string,
changeStatus: "publish", changeStatus: "publish",
data: {catatan: "", senderId: user?.id as string}
}); });
if (!response.success) { if (!response.success) {

View File

@@ -7,6 +7,7 @@ import {
} from "@/components"; } from "@/components";
import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle"; import AdminBackButtonAntTitle from "@/components/_ShareComponent/Admin/BackButtonAntTitle";
import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject"; import AdminButtonReject from "@/components/_ShareComponent/Admin/ButtonReject";
import { useAuth } from "@/hooks/use-auth";
import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus"; import { funUpdateStatusEvent } from "@/screens/Admin/Event/funUpdateStatus";
import { apiAdminEventById } from "@/service/api-admin/api-admin-event"; import { apiAdminEventById } from "@/service/api-admin/api-admin-event";
import { router, useFocusEffect, useLocalSearchParams } from "expo-router"; import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
@@ -14,9 +15,13 @@ import { useCallback, useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function AdminEventRejectInput() { export default function AdminEventRejectInput() {
const { user } = useAuth();
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any>(""); const [data, setData] = useState<any>({
catatan: "",
senderId: "",
});
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
useFocusEffect( useFocusEffect(
@@ -45,10 +50,16 @@ export default function AdminEventRejectInput() {
}) => { }) => {
try { try {
setIsLoading(true); setIsLoading(true);
const newData = {
catatan: data,
senderId: user?.id as string,
};
const response = await funUpdateStatusEvent({ const response = await funUpdateStatusEvent({
id: id as string, id: id as string,
changeStatus, changeStatus,
data: data, data: newData,
}); });
if (!response.success) { if (!response.success) {

View File

@@ -1,20 +1,26 @@
import { import {
AlertDefaultSystem,
BackButton, BackButton,
BaseBox, BaseBox,
DrawerCustom,
MenuDrawerDynamicGrid,
NewWrapper, NewWrapper,
ScrollableCustom, ScrollableCustom,
StackCustom, StackCustom,
TextCustom, TextCustom,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import { IconDot } from "@/components/_Icon/IconComponent";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent"; import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText"; import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } 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 { useAuth } from "@/hooks/use-auth";
import { useNotificationStore } from "@/hooks/use-notification-store"; import { useNotificationStore } from "@/hooks/use-notification-store";
import { apiGetNotificationsById } from "@/service/api-notifications"; import { apiGetNotificationsById } from "@/service/api-notifications";
import { listOfcategoriesAppNotification } from "@/types/type-notification-category"; import { listOfcategoriesAppNotification } from "@/types/type-notification-category";
import { formatChatTime } from "@/utils/formatChatTime"; import { formatChatTime } from "@/utils/formatChatTime";
import { Ionicons } from "@expo/vector-icons";
import { router, Stack, useFocusEffect } from "expo-router"; import { router, Stack, useFocusEffect } from "expo-router";
import _ from "lodash"; import _ from "lodash";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
@@ -70,6 +76,9 @@ export default function AdminNotification() {
const [listData, setListData] = useState<any[]>([]); const [listData, setListData] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false);
const { markAsReadAll } = useNotificationStore();
const handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
@@ -114,12 +123,12 @@ export default function AdminNotification() {
options={{ options={{
title: "Admin Notifikasi", title: "Admin Notifikasi",
headerLeft: () => <BackButton />, headerLeft: () => <BackButton />,
// headerRight: () => ( headerRight: () => (
// <IconPlus <IconDot
// color={MainColor.yellow} color={MainColor.yellow}
// onPress={() => router.push("/test-notifications")} onPress={() => setOpenDrawer(true)}
// /> />
// ), ),
}} }}
/> />
@@ -154,6 +163,51 @@ export default function AdminNotification() {
)) ))
)} )}
</NewWrapper> </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,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");
}
}
}}
/>
</>
);
}

View File

@@ -49,7 +49,8 @@ export default function NotificationInitializer() {
const fcmToken = await getToken(messagingInstance); const fcmToken = await getToken(messagingInstance);
if (!fcmToken) { if (!fcmToken) {
logout(); console.warn("Tidak bisa mendapatkan FCM token");
// logout();
return; return;
} }

View File

@@ -13,3 +13,13 @@ Exp: open ios/HIPMIBADUNG.xcworkspace
perubahan versi : npm version patch perubahan versi : npm version patch
ios: bunx expo prebuild --platform ios ios: bunx expo prebuild --platform ios
android: bunx expo prebuild --platform android 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

View File

@@ -40,6 +40,7 @@ type NotificationContextType = {
notif: Omit<AppNotification, "id" | "isRead" | "timestamp"> notif: Omit<AppNotification, "id" | "isRead" | "timestamp">
) => void; ) => void;
markAsRead: (id: string) => void; markAsRead: (id: string) => void;
markAsReadAll: (id: string) => void;
syncUnreadCount: () => Promise<void>; syncUnreadCount: () => Promise<void>;
}; };
@@ -48,6 +49,7 @@ const NotificationContext = createContext<NotificationContextType>({
unreadCount: 0, unreadCount: 0,
addNotification: () => {}, addNotification: () => {},
markAsRead: () => {}, markAsRead: () => {},
markAsReadAll: () => {},
syncUnreadCount: async () => {}, syncUnreadCount: async () => {},
}); });
@@ -98,7 +100,7 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
const markAsRead = async (id: string) => { const markAsRead = async (id: string) => {
try { try {
const response = await apiNotificationMarkAsRead({ id }); const response = await apiNotificationMarkAsRead({ id, category: "one" });
console.log("🚀 Response Mark As Read:", response); console.log("🚀 Response Mark As Read:", response);
if (response.success) { 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 () => { const syncUnreadCount = async () => {
try { try {
const count = await apiNotificationUnreadCount({ const count = await apiNotificationUnreadCount({
@@ -133,8 +154,9 @@ export const NotificationProvider = ({ children }: { children: ReactNode }) => {
value={{ value={{
notifications, notifications,
addNotification, addNotification,
markAsRead,
unreadCount, unreadCount,
markAsRead,
markAsReadAll,
syncUnreadCount, syncUnreadCount,
}} }}
> >

View File

@@ -1,4 +1,5 @@
import { apiAdminEventUpdateStatus } from "@/service/api-admin/api-admin-event"; import { apiAdminEventUpdateStatus } from "@/service/api-admin/api-admin-event";
import { RejectedData } from "@/types/type-collect-other";
export const funUpdateStatusEvent = async ({ export const funUpdateStatusEvent = async ({
id, id,
@@ -7,15 +8,17 @@ export const funUpdateStatusEvent = async ({
}: { }: {
id: string; id: string;
changeStatus: "publish" | "review" | "reject"; changeStatus: "publish" | "review" | "reject";
data?: string; data?: RejectedData;
}) => { }) => {
try { try {
console.log("[DATA]", data);
const response = await apiAdminEventUpdateStatus({ const response = await apiAdminEventUpdateStatus({
id: id, id: id,
changeStatus: changeStatus as any, changeStatus: changeStatus as any,
data: data, data: data as any,
}); });
return response; return response;
} catch (error) { } catch (error) {
console.log("[ERROR]", error); console.log("[ERROR]", error);
throw error; throw error;

View File

@@ -1,6 +1,6 @@
import { import {
NotificationProp, NotificationProp,
TypeNotificationCategoryApp TypeNotificationCategoryApp,
} from "@/types/type-notification-category"; } from "@/types/type-notification-category";
import { apiConfig } from "./api-config"; 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 { try {
const response = await apiConfig.put(`/mobile/notification/${id}`); const response = await apiConfig.put(
`/mobile/notification/${id}?category=${category}`
);
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;

View File

@@ -0,0 +1,4 @@
export type RejectedData = {
catatan?: string;
senderId: string;
};

View File

@@ -15,21 +15,21 @@ export const formatChatTime = (date: string | Date): string => {
const messageDate = dayjs(date); const messageDate = dayjs(date);
const now = dayjs(); const now = dayjs();
// Jika hari ini // Hari ini
if (messageDate.isSame(now, 'day')) { 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')) { 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) { 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 // Lebih dari 7 hari lalu
return messageDate.format('D MMM YYYY HH:mm'); // contoh: "12 Mei 2024 14:30" return messageDate.format('DD - MM - YYYY, HH.mm'); // "05 - 11 - 2025, 14.00"
}; };