Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions apps/native/src/apis/controller/student/notice/useGetNotice.ts
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down
2 changes: 1 addition & 1 deletion apps/native/src/apis/controller/student/scrap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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('알림', '진행할 문제가 없습니다.');
Expand Down Expand Up @@ -228,4 +231,3 @@ const NotificationScreen = () => {
};

export default NotificationScreen;

159 changes: 70 additions & 89 deletions apps/native/src/features/student/menu/screens/NoticeScreen.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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<NativeStackNavigationProp<StudentRootStackParamList>>();
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] }) => (
<NotificationItem
icon='megaphone'
title={notice.title}
time={formatDate(notice.startAt)}
hasBadge={!notice.isRead}>
<NotificationItem.Button
variant='ghost'
onPress={() => {
if (!notice.isRead) {
readNotice(notice.id);
}
navigation.push('NotificationDetail', {
noticeId: notice.id,
title: notice.title,
date: notice.startAt,
content: notice.content,
});
}}>
더보기
</NotificationItem.Button>
</NotificationItem>
),
[readNotice, navigation]
);

const handleReadAll = () => {
if (unreadNotificationCount >= 1) {
readAllNotifications();
}
};
const keyExtractor = useCallback((item: { id: number }) => String(item.id), []);

return (
<ScreenLayout title='공지사항'>
<ScrollView>
<View className='mx-auto w-full'>
<Container className='gap-[10px] pt-[16px]'>
{notices.map((notice) => (
<NotificationItem
key={notice.id}
icon='megaphone'
title={notice.title}
time={formatDate(notice.startAt)}
hasBadge={!notice.isRead}>
<NotificationItem.Button
variant='ghost'
onPress={() => {
if (!notice.isRead) {
putReadNotice(notice.id).then(() => {
invalidateNoticeData();
});
}
navigation.push('NotificationDetail', {
noticeId: notice.id,
title: notice.title,
date: notice.startAt,
content: notice.content,
});
}}>
더보기
</NotificationItem.Button>
</NotificationItem>
))}
{notices.length === 0 && (
<View className='flex-col items-center gap-[10px] py-[30px]'>
<Text className='text-14m text-gray-600'>공지사항이 없어요.</Text>
</View>
)}
</Container>
{allNotices.length === 0 && !isFetching ? (
<View className='flex-col items-center gap-[10px] py-[30px]'>
<Text className='text-14m text-gray-600'>공지사항이 없어요.</Text>
</View>
<Container className='flex-1 items-center justify-center gap-[10px] pb-[100px] pt-[20px]'>
<Text className='text-14m text-gray-600'>7일 전 알림까지 확인할 수 있어요.</Text>
) : (
<Container>
<FlatList
data={allNotices}
renderItem={renderItem}
keyExtractor={keyExtractor}
onEndReached={handleEndReached}
onEndReachedThreshold={0.3}
contentContainerStyle={{ gap: 10, paddingTop: 16, flexGrow: 1 }}
/>
</Container>
</ScrollView>
)}
</ScreenLayout>
);
};
Expand Down
16 changes: 14 additions & 2 deletions apps/native/src/types/api/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -8622,7 +8631,10 @@ export interface operations {
};
getsAvailable_1: {
parameters: {
query?: never;
query?: {
page?: number;
size?: number;
};
header?: never;
path?: never;
cookie?: never;
Expand All @@ -8635,7 +8647,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
'*/*': components['schemas']['ListRespNoticeResp'];
'*/*': components['schemas']['PageRespNoticeResp'];
};
};
};
Expand Down