diff --git a/src/core/hooks/useNotifications.ts b/src/core/hooks/useNotifications.ts index 90c9eb0d..aa97bd7f 100644 --- a/src/core/hooks/useNotifications.ts +++ b/src/core/hooks/useNotifications.ts @@ -1,5 +1,6 @@ import { useCallback } from 'react'; +import { CourseOverviewPreviousEditionsInner } from '@polito/api-client'; import { Notification } from '@polito/api-client/models/Notification'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -160,6 +161,27 @@ export const useNotifications = () => { [unreadNotifications], ); + const getUnreadsCountPerCourse = useCallback( + ( + courseId?: number | null, + prevEditions?: CourseOverviewPreviousEditionsInner[], + ) => { + if (courseId === undefined || !prevEditions) return 0; + const courseIds = prevEditions.map(e => e.id); + if (courseId) { + courseIds.push(courseId); + } + return ( + courseIds.reduce( + (acc, eid) => + acc + (getUnreadsCount(['teaching', 'courses', eid]) ?? 0), + 0, + ) || undefined + ); + }, + [getUnreadsCount], + ); + const navigateToUpdate = useCallback( (notification?: RemoteMessage) => { if (!notification || !notification.data?.polito_transazione) { @@ -216,5 +238,6 @@ export const useNotifications = () => { navigateToUpdate, clearNotificationScope, getUnreadsCount, + getUnreadsCountPerCourse, }; }; diff --git a/src/core/queries/courseHooks.ts b/src/core/queries/courseHooks.ts index feb03b8e..626db0f3 100644 --- a/src/core/queries/courseHooks.ts +++ b/src/core/queries/courseHooks.ts @@ -12,7 +12,6 @@ import { CoursesApi, UploadCourseAssignmentRequest, } from '@polito/api-client'; -import { MenuAction } from '@react-native-menu/menu'; import { useMutation, useQueries, @@ -158,41 +157,21 @@ export const useGetCourseEditions = (courseId: number) => { c => c.id === courseId || c.previousEditions.some(e => e.id === courseId), ); - const editions: MenuAction[] = []; + const editions: CourseOverviewPreviousEditionsInner[] = []; if (!course || !course.previousEditions.length) return editions; if (course.id) { - editions.push( - { - id: `${course.id}`, - title: course.year, - state: courseId === course?.id ? 'on' : undefined, - }, - ...course.previousEditions.map( - e => - ({ - id: `${e.id}`, - title: e.year, - state: courseId === e.id ? 'on' : undefined, - } as MenuAction), - ), - ); + editions.push({ + id: course.id, + year: course.year, + }); + editions.push(...course.previousEditions); } else { const prevEditions = course.previousEditions .filter(e => e.id !== null) .sort((a, b) => +b.year - +a.year) .slice(1); - editions.push( - ...prevEditions.map( - e => - ({ - id: `${e.id}`, - title: e.year, - state: courseId === e.id ? 'on' : undefined, - } as MenuAction), - ), - ); + editions.push(...prevEditions); } - return editions; }, { diff --git a/src/features/courses/components/CourseListItem.tsx b/src/features/courses/components/CourseListItem.tsx index f26e683f..bcd08ac6 100644 --- a/src/features/courses/components/CourseListItem.tsx +++ b/src/features/courses/components/CourseListItem.tsx @@ -119,8 +119,6 @@ export const CourseListItem = ({ screen: 'Course', params: { id: courseInfo?.id, - courseName: courseInfo?.name, - uniqueShortcode: courseInfo?.uniqueShortcode, }, } : undefined diff --git a/src/features/courses/navigation/CourseNavigator.tsx b/src/features/courses/navigation/CourseNavigator.tsx index dfa0502b..7dda5470 100644 --- a/src/features/courses/navigation/CourseNavigator.tsx +++ b/src/features/courses/navigation/CourseNavigator.tsx @@ -45,7 +45,6 @@ export const CourseNavigator = ({ route, navigation }: Props) => { const titleStyles = useTitlesStyles(theme); const { id } = route.params; - const coursesQuery = useGetCourses(); useEffect(() => { diff --git a/src/features/courses/screens/CourseInfoScreen.tsx b/src/features/courses/screens/CourseInfoScreen.tsx index 92a095a1..97fc423b 100644 --- a/src/features/courses/screens/CourseInfoScreen.tsx +++ b/src/features/courses/screens/CourseInfoScreen.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Linking, @@ -7,10 +7,13 @@ import { StyleSheet, View, } from 'react-native'; +import { Platform } from 'react-native'; import { faAngleDown } from '@fortawesome/free-solid-svg-icons'; import { faLink } from '@fortawesome/free-solid-svg-icons'; +import { faCircle } from '@fortawesome/free-solid-svg-icons'; import { Card } from '@lib/ui/components/Card'; +import { Col } from '@lib/ui/components/Col'; import { Grid } from '@lib/ui/components/Grid'; import { Icon } from '@lib/ui/components/Icon'; import { ListItem } from '@lib/ui/components/ListItem'; @@ -28,11 +31,13 @@ import { useStylesheet } from '@lib/ui/hooks/useStylesheet'; import { useTheme } from '@lib/ui/hooks/useTheme'; import { Theme } from '@lib/ui/types/Theme'; import { Person } from '@polito/api-client/models/Person'; +import { MenuAction } from '@react-native-menu/menu'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useQueryClient } from '@tanstack/react-query'; import { BottomBarSpacer } from '../../../core/components/BottomBarSpacer'; +import { useNotifications } from '../../../core/hooks/useNotifications'; import { useOfflineDisabled } from '../../../core/hooks/useOfflineDisabled'; import { CourseSectionEnum, @@ -53,6 +58,8 @@ export const CourseInfoScreen = () => { const { t } = useTranslation(); const courseId = useCourseContext(); const styles = useStylesheet(createStyles); + const { spacing, palettes } = useTheme(); + const { getUnreadsCount, getUnreadsCountPerCourse } = useNotifications(); const { fontSizes } = useTheme(); const [staff, setStaff] = useState([]); const { data: editions } = useGetCourseEditions(courseId); @@ -65,10 +72,31 @@ export const CourseInfoScreen = () => { courseQuery.data?.staff.map(s => s.id), ); + const unreadsCurrentYear = getUnreadsCount(['teaching', 'courses', courseId]); + const unreadsPrevEditions = + (getUnreadsCountPerCourse(null, editions) ?? 0) - (unreadsCurrentYear ?? 0); + const isOffline = useOfflineDisabled(); const { getParent } = useNavigation(); + const menuActions = useMemo(() => { + if (!editions) return []; + return editions.map(e => { + const editionsCount = getUnreadsCount(['teaching', 'courses', e.id]); + return { + id: `${e.id}`, + title: e.year, + state: courseId === e.id ? 'on' : undefined, + image: editionsCount + ? Platform.select({ ios: 'circle.fill', android: 'circle' }) + : undefined, + imageColor: editionsCount ? palettes.rose[600] : undefined, + } as MenuAction; + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editions, courseId]); + useEffect(() => { if (!courseQuery.data || isStaffLoading) { return; @@ -125,7 +153,7 @@ export const CourseInfoScreen = () => { accessible={true} > { // replace current screen with same screen with event id as param ( @@ -148,15 +176,30 @@ export const CourseInfoScreen = () => { accessibilityLabel={`${t('degreeCourseScreen.period')}: ${ courseQuery.data?.teachingPeriod ?? '--' } - ${courseQuery.data?.year ?? '--'}`} + style={styles.periodMetric} /> - {(editions?.length ?? 0) > 0 && ( - - )} + + {unreadsPrevEditions > 0 && ( + + )} + {(editions?.length ?? 0) > 0 && ( + + )} + @@ -267,9 +310,14 @@ const createStyles = ({ palettes, spacing }: Theme) => marginTop: 0, marginBottom: spacing[7], }, + periodMetric: { + marginRight: spacing[2], + }, periodDropdownIcon: { - marginLeft: spacing[2], - marginTop: spacing[4], color: palettes.secondary['500'], }, + dotIcon: { + marginBottom: spacing[2], + color: palettes.rose['600'], + }, }); diff --git a/src/features/teaching/screens/TeachingScreen.tsx b/src/features/teaching/screens/TeachingScreen.tsx index 8b89256d..6eabfec8 100644 --- a/src/features/teaching/screens/TeachingScreen.tsx +++ b/src/features/teaching/screens/TeachingScreen.tsx @@ -55,7 +55,7 @@ export const TeachingScreen = ({ navigation }: Props) => { const styles = useStylesheet(createStyles); const { courses: coursePreferences, hideGrades } = usePreferencesContext(); const isOffline = useOfflineDisabled(); - const { getUnreadsCount } = useNotifications(); + const { getUnreadsCountPerCourse } = useNotifications(); const surveyCategoriesQuery = useGetSurveyCategories(); const coursesQuery = useGetCourses(); const examsQuery = useGetExams(); @@ -147,15 +147,10 @@ export const TeachingScreen = ({ navigation }: Props) => { ))}