diff --git a/apps/native/src/apis/controller/student/notice/useGetNotice.ts b/apps/native/src/apis/controller/student/notice/useGetNotice.ts index e6428aaa..cd86023e 100644 --- a/apps/native/src/apis/controller/student/notice/useGetNotice.ts +++ b/apps/native/src/apis/controller/student/notice/useGetNotice.ts @@ -1,7 +1,14 @@ import { TanstackQueryClient } from '@/apis/client'; +import { paths } from '@/types/api/schema'; -const useGetNotice = () => { - return TanstackQueryClient.useQuery('get', '/api/student/notice', {}); +type UseGetNoticeParams = paths['/api/student/notice']['get']['parameters']['query']; + +const useGetNotice = (params?: UseGetNoticeParams) => { + return TanstackQueryClient.useQuery('get', '/api/student/notice', { + params: { + query: params, + }, + }); }; export default useGetNotice; diff --git a/apps/native/src/apis/controller/student/notice/useInvalidateNoticeData.ts b/apps/native/src/apis/controller/student/notice/useInvalidateNoticeData.ts index 5e18feb4..90273ba5 100644 --- a/apps/native/src/apis/controller/student/notice/useInvalidateNoticeData.ts +++ b/apps/native/src/apis/controller/student/notice/useInvalidateNoticeData.ts @@ -7,14 +7,16 @@ const useInvalidateNoticeData = () => { const invalidateNoticeCount = useCallback(() => { return queryClient.invalidateQueries({ - queryKey: TanstackQueryClient.queryOptions('get', '/api/student/notice/count', {}) - .queryKey, + queryKey: TanstackQueryClient.queryOptions('get', '/api/student/notice/count', {}).queryKey, }); }, [queryClient]); const invalidateNotice = useCallback(() => { return queryClient.invalidateQueries({ - queryKey: TanstackQueryClient.queryOptions('get', '/api/student/notice', {}).queryKey, + predicate: (query) => + Array.isArray(query.queryKey) && + query.queryKey[0] === 'get' && + query.queryKey[1] === '/api/student/notice', }); }, [queryClient]); diff --git a/apps/native/src/apis/controller/student/scrap/index.ts b/apps/native/src/apis/controller/student/scrap/index.ts index 5d1d359a..ab489de2 100644 --- a/apps/native/src/apis/controller/student/scrap/index.ts +++ b/apps/native/src/apis/controller/student/scrap/index.ts @@ -4,7 +4,7 @@ export * from './useGetFoldersDetail'; export * from './useGetFolders'; export * from './useGetTrash'; export * from './useSearchScraps'; -export * from './useGetScrapsByFolder'; +export * from './getScrapsByFolder'; export * from './handwriting/useGetHandwriting'; export * from './useGetScrapStatusById'; diff --git a/apps/native/src/features/student/home/screens/notifications/NotificationsScreen.tsx b/apps/native/src/features/student/home/screens/notifications/NotificationsScreen.tsx index d3f6bd7e..631f1f2f 100644 --- a/apps/native/src/features/student/home/screens/notifications/NotificationsScreen.tsx +++ b/apps/native/src/features/student/home/screens/notifications/NotificationsScreen.tsx @@ -47,7 +47,7 @@ const NotificationScreen = () => { const queryClient = useQueryClient(); const isTablet = useIsTablet(); - const { data: noticeData } = useGetNotice(); + const { data: noticeData } = useGetNotice({ size: 2 }); const { data: notificationData } = useGetNotification({ dayLimit: 7 }); const { data: notificationCountData } = useGetNotificationCount({}); const { invalidateAll: invalidateNotifications } = useInvalidateNotificationData(); @@ -77,7 +77,11 @@ const NotificationScreen = () => { } }; - const handleNotificationPress = async (notificationId: number, url?: string, isRead?: boolean) => { + const handleNotificationPress = async ( + notificationId: number, + url?: string, + isRead?: boolean + ) => { if (!isRead) { readNotification(notificationId); } @@ -117,8 +121,7 @@ const NotificationScreen = () => { } const groups = publishDetail.data ?? []; - const targetGroup = - groups.find((group) => group.progress !== 'DONE') ?? groups[0]; + const targetGroup = groups.find((group) => group.progress !== 'DONE') ?? groups[0]; if (!targetGroup) { Alert.alert('알림', '진행할 문제가 없습니다.'); @@ -228,4 +231,3 @@ const NotificationScreen = () => { }; export default NotificationScreen; - diff --git a/apps/native/src/features/student/menu/screens/NoticeScreen.tsx b/apps/native/src/features/student/menu/screens/NoticeScreen.tsx index 41565210..5f0e1199 100644 --- a/apps/native/src/features/student/menu/screens/NoticeScreen.tsx +++ b/apps/native/src/features/student/menu/screens/NoticeScreen.tsx @@ -1,20 +1,11 @@ -import React from 'react'; -import { View, Text, ScrollView } from 'react-native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { View, Text, FlatList } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Container, NotificationItem } from '@components/common'; import { ScreenLayout } from '../components'; -import { putReadNotice, useGetNotice } from '@apis'; -import { - useGetNotification, - useGetNotificationCount, - usePostReadAllNotification, - usePostReadNotification, -} from '@/apis/controller/student/notification'; +import { putReadNotice, useGetNotice, useInvalidateNoticeData } from '@apis'; import { StudentRootStackParamList } from '@/navigation/student/types'; -import { useQueryClient } from '@tanstack/react-query'; -import { TanstackQueryClient } from '@apis'; -import useInvalidateNotificationData from '@/apis/controller/student/notification/useIncalidateNotificationData'; const formatDate = (dateString: string) => { const date = new Date(dateString); @@ -28,99 +19,89 @@ const formatDate = (dateString: string) => { return `${date.getMonth() + 1}월 ${date.getDate()}일`; }; -const getNotificationIcon = (type: string): 'megaphone' | 'message' | 'book' => { - switch (type) { - case 'QNA': - return 'message'; - case 'ASSIGNMENT': - return 'book'; - default: - return 'megaphone'; - } -}; +const PAGE_SIZE = 20; const NoticeScreen = () => { const navigation = useNavigation>(); - const queryClient = useQueryClient(); + const [page, setPage] = useState(0); + const [allNotices, setAllNotices] = useState< + Array<{ id: number; title: string; startAt: string; content: string; isRead: boolean }> + >([]); - const { data: noticeData } = useGetNotice(); - const { data: notificationData } = useGetNotification({ dayLimit: 7 }); - const { data: notificationCountData } = useGetNotificationCount({}); - const { invalidateAll } = useInvalidateNotificationData(); + const { data: noticeData, isFetching } = useGetNotice({ page, size: PAGE_SIZE }); + const { invalidateNoticeCount, invalidateNotice } = useInvalidateNoticeData(); - const invalidateNoticeData = () => { - queryClient.invalidateQueries({ - queryKey: TanstackQueryClient.queryOptions('get', '/api/student/notice', {}).queryKey, - }); - queryClient.invalidateQueries({ - queryKey: TanstackQueryClient.queryOptions('get', '/api/student/notice/count', {}).queryKey, + useEffect(() => { + const list = noticeData?.data; + const dataPage = noticeData?.page != null ? Number(noticeData.page) : undefined; + if (!Array.isArray(list) || dataPage === undefined || dataPage !== page) return; + if (page === 0) { + setAllNotices(list); + } else { + setAllNotices((prev) => [...prev.slice(0, page * PAGE_SIZE), ...list]); + } + }, [noticeData, page]); + + const readNotice = (noticeIds: number) => { + putReadNotice(noticeIds).then(() => { + invalidateNoticeCount(); + invalidateNotice(); }); }; - const { mutate: readAllNotifications } = usePostReadAllNotification({ - onSuccess: () => { - invalidateAll(); - }, - }); + const hasMore = noticeData ? page < noticeData.lastPage : false; - const { mutate: readNotification } = usePostReadNotification({ - onSuccess: () => { - invalidateAll(); - }, - }); + const handleEndReached = useCallback(() => { + if (hasMore && !isFetching) setPage((p) => p + 1); + }, [hasMore, isFetching]); - const notices = noticeData?.data ?? []; - const notifications = notificationData?.data ?? []; - const unreadNotificationCount = notificationCountData?.unreadCount ?? 0; + const renderItem = useCallback( + ({ item: notice }: { item: (typeof allNotices)[number] }) => ( + + { + if (!notice.isRead) { + readNotice(notice.id); + } + navigation.push('NotificationDetail', { + noticeId: notice.id, + title: notice.title, + date: notice.startAt, + content: notice.content, + }); + }}> + 더보기 + + + ), + [readNotice, navigation] + ); - const handleReadAll = () => { - if (unreadNotificationCount >= 1) { - readAllNotifications(); - } - }; + const keyExtractor = useCallback((item: { id: number }) => String(item.id), []); return ( - - - - {notices.map((notice) => ( - - { - if (!notice.isRead) { - putReadNotice(notice.id).then(() => { - invalidateNoticeData(); - }); - } - navigation.push('NotificationDetail', { - noticeId: notice.id, - title: notice.title, - date: notice.startAt, - content: notice.content, - }); - }}> - 더보기 - - - ))} - {notices.length === 0 && ( - - 공지사항이 없어요. - - )} - + {allNotices.length === 0 && !isFetching ? ( + + 공지사항이 없어요. - - 7일 전 알림까지 확인할 수 있어요. + ) : ( + + - + )} ); }; diff --git a/apps/native/src/types/api/schema.d.ts b/apps/native/src/types/api/schema.d.ts index f9f46748..88c0835e 100644 --- a/apps/native/src/types/api/schema.d.ts +++ b/apps/native/src/types/api/schema.d.ts @@ -4324,6 +4324,15 @@ export interface components { unreadCount: number; latestNotification?: components['schemas']['NotificationResp']; }; + PageRespNoticeResp: { + /** Format: int32 */ + page: number; + /** Format: int32 */ + size: number; + /** Format: int32 */ + lastPage: number; + data: components['schemas']['NoticeResp'][]; + }; NoticeUnreadCountResp: { /** Format: int64 */ totalCount?: number; @@ -8622,7 +8631,10 @@ export interface operations { }; getsAvailable_1: { parameters: { - query?: never; + query?: { + page?: number; + size?: number; + }; header?: never; path?: never; cookie?: never; @@ -8635,7 +8647,7 @@ export interface operations { [name: string]: unknown; }; content: { - '*/*': components['schemas']['ListRespNoticeResp']; + '*/*': components['schemas']['PageRespNoticeResp']; }; }; };