import AppHeader from "@/components/AppHeader"; import ModalConfirmation from "@/components/ModalConfirmation"; import SkeletonTwoItem from "@/components/skeletonTwoItem"; import Text from "@/components/Text"; import Styles from "@/constants/Styles"; import { apiGetNotification, apiReadAllNotification, apiReadOneNotification } from "@/lib/api"; import { setUpdateNotification } from "@/lib/notificationSlice"; import { pushToPage } from "@/lib/pushToPage"; import { useAuthSession } from "@/providers/AuthProvider"; import { useTheme } from "@/providers/ThemeProvider"; import { Feather } from "@expo/vector-icons"; import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; import { router, Stack } from "expo-router"; import { useEffect, useMemo, useState } from "react"; import { FlatList, Pressable, RefreshControl, SafeAreaView, View } from "react-native"; import { useDispatch, useSelector } from "react-redux"; type Props = { id: string title: string desc: string category: string idContent: string isRead: boolean createdAt: string } type HeaderRow = { _type: 'header'; date: string } type ItemRow = Props & { _type: 'item' } type ListRow = HeaderRow | ItemRow function getNotifStyle(category: string): { icon: keyof typeof Feather.glyphMap; color: string } { if (category === 'announcement') return { icon: 'volume-2', color: '#3B82F6' } if (category === 'project') return { icon: 'activity', color: '#10B981' } if (category.includes('/task')) return { icon: 'clipboard', color: '#8B5CF6' } if (category === 'division') return { icon: 'users', color: '#3B82F6' } if (category.includes('/discussion') || category === 'discussion-general') return { icon: 'message-square', color: '#06B6D4' } if (category.includes('/calendar')) return { icon: 'calendar', color: '#F59E0B' } if (category.includes('/document')) return { icon: 'file-text', color: '#FBBF24' } if (category === 'member') return { icon: 'user', color: '#1F3C88' } return { icon: 'bell', color: '#6B7280' } } export default function Notification() { const { token, decryptToken } = useAuthSession() const { colors } = useTheme(); const queryClient = useQueryClient() const dispatch = useDispatch() const updateNotification = useSelector((state: any) => state.notificationUpdate) const [refreshing, setRefreshing] = useState(false) const [markingAll, setMarkingAll] = useState(false) const [showConfirm, setShowConfirm] = useState(false) const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, refetch } = useInfiniteQuery({ queryKey: ['notifications'], queryFn: async ({ pageParam = 1 }) => { const hasil = await decryptToken(String(token?.current)) const response = await apiGetNotification({ user: hasil, page: pageParam }) return response; }, initialPageParam: 1, getNextPageParam: (lastPage, allPages) => { return lastPage.data.length > 0 ? allPages.length + 1 : undefined; }, enabled: !!token?.current, staleTime: 0, }) const flatData = useMemo(() => { return data?.pages.flatMap(page => page.data) || []; }, [data]) const listData = useMemo(() => { const groups: Record = {} const dateOrder: string[] = [] flatData.forEach((item) => { if (!groups[item.createdAt]) { groups[item.createdAt] = [] dateOrder.push(item.createdAt) } groups[item.createdAt].push(item) }) const result: ListRow[] = [] dateOrder.forEach((date) => { result.push({ _type: 'header', date }) const sorted = [...groups[date]].sort((a, b) => Number(a.isRead) - Number(b.isRead)) sorted.forEach((item) => result.push({ ...item, _type: 'item' })) }) return result }, [flatData]) useEffect(() => { refetch() }, [updateNotification, refetch]) const handleRefresh = async () => { setRefreshing(true) await queryClient.invalidateQueries({ queryKey: ['notifications'] }) setRefreshing(false) }; const hasUnread = flatData.some((item) => !item.isRead) async function handleReadAll() { try { setMarkingAll(true) const hasil = await decryptToken(String(token?.current)) await apiReadAllNotification({ user: hasil }) await queryClient.invalidateQueries({ queryKey: ['notifications'] }) dispatch(setUpdateNotification(!updateNotification)) } catch (error) { console.error(error) } finally { setMarkingAll(false) } } async function handleReadNotification(id: string, category: string, idContent: string) { try { const hasil = await decryptToken(String(token?.current)) await apiReadOneNotification({ user: hasil, id: id }) await queryClient.invalidateQueries({ queryKey: ['notifications'] }) pushToPage(category, idContent) dispatch(setUpdateNotification(!updateNotification)) } catch (error) { console.error(error) } } async function handleMarkOneRead(id: string) { try { const hasil = await decryptToken(String(token?.current)) await apiReadOneNotification({ user: hasil, id: id }) await queryClient.invalidateQueries({ queryKey: ['notifications'] }) dispatch(setUpdateNotification(!updateNotification)) } catch (error) { console.error(error) } } return ( ( router.back()} right={ hasUnread ? ( setShowConfirm(true)} disabled={markingAll} style={{ opacity: markingAll ? 0.5 : 1, padding: 4 }} > ) : undefined } /> ) }} /> { setShowConfirm(false) handleReadAll() }} onCancel={() => setShowConfirm(false)} /> {isLoading ? ( [0, 1, 2, 3, 4].map((_, i) => ) ) : flatData.length === 0 ? ( Tidak ada notifikasi ) : ( String(index)} showsVerticalScrollIndicator={false} onEndReached={() => { if (hasNextPage && !isFetchingNextPage) fetchNextPage() }} onEndReachedThreshold={0.5} refreshControl={ } renderItem={({ item }) => { if (item._type === 'header') { return ( {item.date} ) } const { icon, color } = getNotifStyle(item.category) return ( handleReadNotification(item.id, item.category, item.idContent)} style={({ pressed }) => [Styles.notifItemRow, { borderColor: colors.icon + '20', backgroundColor: pressed ? colors.icon + '10' : item.isRead ? colors.icon + '10' : colors.card, }]} > {item.title} {!item.isRead && ( { e.stopPropagation() handleMarkOneRead(item.id) }} hitSlop={8} style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1, flexShrink: 0 })} > Tandai dibaca )} {item.desc} ) }} /> )} ) }