Fix job notifikasi

###  No Issue
This commit is contained in:
2026-01-08 10:12:53 +08:00
parent 7c85e35c61
commit 145ad73616
10 changed files with 163 additions and 70 deletions

View File

@@ -519,7 +519,8 @@ export default function UserLayout() {
name="job/(tabs)" name="job/(tabs)"
options={{ options={{
title: "Job Vacancy", title: "Job Vacancy",
headerLeft: () => <BackButton path="/home" />, // headerLeft: () => <BackButton path="/home" />,
// Note: headerLeft di pindahkan ke Tabs Layout
}} }}
/> />
<Stack.Screen <Stack.Screen

View File

@@ -1,34 +1,72 @@
import { BackButton } from "@/components";
import { IconHome, IconStatus } from "@/components/_Icon"; import { IconHome, IconStatus } from "@/components/_Icon";
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 { Tabs } from "expo-router"; import {
Stack,
Tabs,
useLocalSearchParams,
router,
useNavigation,
} from "expo-router";
import { useLayoutEffect } from "react";
export default function JobTabsLayout() { export default function JobTabsLayout() {
const navigation = useNavigation();
const { from, category } = useLocalSearchParams<{
from?: string;
category?: string;
}>();
// Atur header secara dinamis
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<BackButton
onPress={() => {
if (from === "notifications") {
router.replace(`/notifications?category=${category}`);
} else {
if (from) {
router.replace(`/${from}` as any);
} else {
router.back();
}
}
}}
/>
),
});
}, [from, router, navigation]);
return ( return (
<Tabs screenOptions={TabsStyles}> <>
<Tabs.Screen <Tabs screenOptions={TabsStyles}>
name="index" <Tabs.Screen
options={{ name="index"
title: "Beranda", options={{
tabBarIcon: ({ color }) => <IconHome color={color} />, title: "Beranda",
}} tabBarIcon: ({ color }) => <IconHome color={color} />,
/> }}
<Tabs.Screen />
name="status" <Tabs.Screen
options={{ name="status"
title: "Status", options={{
tabBarIcon: ({ color }) => <IconStatus color={color} />, title: "Status",
}} tabBarIcon: ({ color }) => <IconStatus color={color} />,
/> }}
<Tabs.Screen />
name="archive" <Tabs.Screen
options={{ name="archive"
title: "Arsip", options={{
tabBarIcon: ({ color }) => ( title: "Arsip",
<Ionicons size={20} name="archive" color={color} /> tabBarIcon: ({ color }) => (
), <Ionicons size={20} name="archive" color={color} />
}} ),
/> }}
</Tabs> />
</Tabs>
</>
); );
} }

View File

@@ -9,14 +9,17 @@ import {
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 { apiJobGetByStatus } from "@/service/api-client/api-job"; import { apiJobGetByStatus } from "@/service/api-client/api-job";
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 JobStatus() { export default function JobStatus() {
const { user } = useAuth(); const { user } = useAuth();
const { status } = useLocalSearchParams<{ status?: string }>();
console.log("STATUS", status);
const [activeCategory, setActiveCategory] = useState<string | null>( const [activeCategory, setActiveCategory] = useState<string | null>(
"publish" status || "publish"
); );
const [listData, setListData] = useState<any[]>([]); const [listData, setListData] = useState<any[]>([]);
const [isLoadList, setIsLoadList] = useState(false); const [isLoadList, setIsLoadList] = useState(false);
@@ -60,25 +63,29 @@ export default function JobStatus() {
); );
return ( return (
<ViewWrapper headerComponent={scrollComponent} hideFooter> <>
{isLoadList ? ( <ViewWrapper headerComponent={scrollComponent} hideFooter>
<LoaderCustom /> {isLoadList ? (
) : _.isEmpty(listData) ? ( <LoaderCustom />
<TextCustom align="center">Tidak ada data {activeCategory}</TextCustom> ) : _.isEmpty(listData) ? (
) : ( <TextCustom align="center">
listData.map((e, i) => ( Tidak ada data {activeCategory}
<BaseBox </TextCustom>
key={i} ) : (
paddingTop={20} listData.map((e, i) => (
paddingBottom={20} <BaseBox
href={`/job/${e?.id}/${activeCategory}/detail`} key={i}
> paddingTop={20}
<TextCustom align="center" bold truncate size="large"> paddingBottom={20}
{e?.title} href={`/job/${e?.id}/${activeCategory}/detail`}
</TextCustom> >
</BaseBox> <TextCustom align="center" bold truncate size="large">
)) {e?.title}
)} </TextCustom>
</ViewWrapper> </BaseBox>
))
)}
</ViewWrapper>
</>
); );
} }

View File

@@ -74,7 +74,7 @@ export default function JobDetailStatus() {
<StackCustom gap={"xs"}> <StackCustom gap={"xs"}>
{data && {data &&
data?.catatan && data?.catatan &&
(status === "draft" || status === "rejected") && ( (status === "draft" || status === "reject") && (
<ReportBox text={data?.catatan} /> <ReportBox text={data?.catatan} />
)} )}

View File

@@ -19,7 +19,7 @@ import { useState } from "react";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
export default function JobCreate() { export default function JobCreate() {
const nextUrl = "/(application)/(user)/job/(tabs)/status"; const nextUrl = "/(application)/(user)/job/(tabs)/status?status=review";
const { user } = useAuth(); const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [image, setImage] = useState<string | null>(null); const [image, setImage] = useState<string | null>(null);

View File

@@ -13,7 +13,7 @@ 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 } from "expo-router"; import { router, 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";
@@ -25,6 +25,19 @@ const selectedCategory = (value: string) => {
return category?.label; return category?.label;
}; };
const fixPath = ({
deepLink,
categoryApp,
}: {
deepLink: string;
categoryApp: string;
}) => {
const fixPath =
deepLink + "&from=notifications&category=" + _.lowerCase(categoryApp);
return fixPath;
};
const BoxNotification = ({ const BoxNotification = ({
data, data,
activeCategory, activeCategory,
@@ -32,18 +45,28 @@ const BoxNotification = ({
data: any; data: any;
activeCategory: string | null; activeCategory: string | null;
}) => { }) => {
// console.log("DATA NOTIFICATION", JSON.stringify(data, null, 2));
const { markAsRead } = useNotificationStore(); const { markAsRead } = useNotificationStore();
return ( return (
<> <>
<BaseBox <BaseBox
backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue} backgroundColor={data.isRead ? AccentColor.darkblue : AccentColor.blue}
onPress={() => { onPress={() => {
console.log( // console.log(
"Notification >", // "Notification >",
selectedCategory(activeCategory as string) // selectedCategory(activeCategory as string)
); // );
router.push(data.deepLink); const newPath = fixPath({
markAsRead(data.id); deepLink: data.deepLink,
categoryApp: data.kategoriApp,
});
router.replace(newPath as any);
selectedCategory(activeCategory as string);
if (!data.isRead) {
markAsRead(data.id);
}
}} }}
> >
<StackCustom> <StackCustom>
@@ -64,7 +87,10 @@ const BoxNotification = ({
export default function Notifications() { export default function Notifications() {
const { user } = useAuth(); const { user } = useAuth();
const [activeCategory, setActiveCategory] = useState<string | null>("event"); const { category } = useLocalSearchParams<{ category?: string }>();
const [activeCategory, setActiveCategory] = useState<string | null>(
category || "event"
);
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);

View File

@@ -15,7 +15,10 @@ import Toast from "react-native-toast-message";
export default function AdminJobRejectInput() { export default function AdminJobRejectInput() {
const { id, status } = useLocalSearchParams(); const { id, status } = useLocalSearchParams();
const [data, setData] = useState<any | null>(null); const [data, setData] = useState({
catatan: "",
senderId: ""
});
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
useFocusEffect( useFocusEffect(
@@ -48,7 +51,7 @@ export default function AdminJobRejectInput() {
const response = await funUpdateStatusJob({ const response = await funUpdateStatusJob({
id: id as string, id: id as string,
changeStatus, changeStatus,
data: data, data: data ,
}); });
if (!response.success) { if (!response.success) {
@@ -102,8 +105,8 @@ export default function AdminJobRejectInput() {
headerComponent={<AdminBackButtonAntTitle title="Penolakan Job" />} headerComponent={<AdminBackButtonAntTitle title="Penolakan Job" />}
> >
<TextAreaCustom <TextAreaCustom
value={data} value={data?.catatan}
onChangeText={setData} onChangeText={(text) => setData({ ...data, catatan: text })}
placeholder="Masukan alasan" placeholder="Masukan alasan"
required required
showCount showCount

View File

@@ -7,6 +7,8 @@ import {
TextCustom, TextCustom,
} from "@/components"; } from "@/components";
import { IconPlus } from "@/components/_Icon"; import { IconPlus } from "@/components/_Icon";
import ListSkeletonComponent from "@/components/_ShareComponent/ListSkeletonComponent";
import NoDataText from "@/components/_ShareComponent/NoDataText";
import { AccentColor, MainColor } from "@/constants/color-palet"; import { AccentColor, MainColor } from "@/constants/color-palet";
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";
@@ -14,6 +16,7 @@ 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, Stack, useFocusEffect } from "expo-router"; import { router, Stack, useFocusEffect } from "expo-router";
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";
@@ -66,6 +69,7 @@ export default function AdminNotification() {
const [activeCategory, setActiveCategory] = useState<string | null>("event"); const [activeCategory, setActiveCategory] = useState<string | null>("event");
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 handlePress = (item: any) => { const handlePress = (item: any) => {
setActiveCategory(item.value); setActiveCategory(item.value);
@@ -80,6 +84,7 @@ export default function AdminNotification() {
const fecthData = async () => { const fecthData = async () => {
try { try {
setLoading(true);
const response = await apiGetNotificationsById({ const response = await apiGetNotificationsById({
id: user?.id as any, id: user?.id as any,
category: activeCategory as any, category: activeCategory as any,
@@ -92,6 +97,8 @@ export default function AdminNotification() {
} }
} catch (error) { } catch (error) {
console.log("Error Notification", error); console.log("Error Notification", error);
} finally {
setLoading(false);
} }
}; };
@@ -132,11 +139,20 @@ export default function AdminNotification() {
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
} }
> >
{listData.map((e, i) => ( {loading ? (
<View key={i}> <ListSkeletonComponent />
<BoxNotification data={e} activeCategory={activeCategory as any} /> ) : _.isEmpty(listData) ? (
</View> <NoDataText text="Belum ada notifikasi" />
))} ) : (
listData.map((e, i) => (
<View key={i}>
<BoxNotification
data={e}
activeCategory={activeCategory as any}
/>
</View>
))
)}
</NewWrapper> </NewWrapper>
</> </>
); );

View File

@@ -12,10 +12,12 @@ const LeftButtonCustom = ({
path, path,
icon = "arrow-back", icon = "arrow-back",
iconCustom, iconCustom,
onPress,
}: { }: {
path?: Href; path?: Href;
icon?: React.ReactNode | any; icon?: React.ReactNode | any;
iconCustom?: React.ReactNode; iconCustom?: React.ReactNode;
onPress?: () => void;
}) => { }) => {
return ( return (
<> <>
@@ -26,7 +28,7 @@ const LeftButtonCustom = ({
name={icon} name={icon}
size={20} size={20}
color={MainColor.yellow} color={MainColor.yellow}
onPress={() => (path ? router.replace(path) : router.back())} onPress={() => (onPress ? onPress() : path ? router.replace(path) : router.back())}
/> />
)} )}
</> </>

View File

@@ -7,7 +7,7 @@ const funUpdateStatusJob = async ({
}: { }: {
id: string; id: string;
changeStatus: "publish" | "review" | "reject"; changeStatus: "publish" | "review" | "reject";
data?: string; data?: any;
}) => { }) => {
try { try {
const response = await apiAdminJobUpdate({ const response = await apiAdminJobUpdate({