From 2f3a9bd7b390606c24fe4138f3b1212939a184bb Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 10 Jan 2026 12:12:00 +0100 Subject: [PATCH 1/9] Added search features and cleaned code Added date and grade score in search algorithm. Used some() instead of filter(). Added null check for grades. Imported GradeScore. Cleaned code for improved readability. --- app/(tabs)/grades/index.tsx | 76 +++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index ff73d1f8c..481654bca 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -11,7 +11,7 @@ import Reanimated, { LinearTransition, useAnimatedStyle } from 'react-native-rea import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { getManager, subscribeManagerUpdate } from '@/services/shared'; -import { Period, Subject } from "@/services/shared/grade"; +import { GradeScore, Period, Subject } from "@/services/shared/grade"; import ChipButton from '@/ui/components/ChipButton'; import { CompactGrade } from '@/ui/components/CompactGrade'; import { Dynamic } from '@/ui/components/Dynamic'; @@ -137,11 +137,14 @@ const GradesView: React.FC = () => { setGradesLoading(true); if (period && managerToUse) { const grades = await managerToUse.getGradesForPeriod(period, period.createdByAccount); + if (!grades || !grades.subjects) { setGradesLoading(false); return; } + setSubjects(grades.subjects); + if (grades.studentOverall && typeof grades.studentOverall.value === 'number') { setServiceAverage(grades.studentOverall.value) } else { @@ -182,8 +185,11 @@ const GradesView: React.FC = () => { // Sort grades in subjects by date descending then sort subjects by latest grade descending const sortedSubjects = useMemo(() => { const subjectsCopy = [...subjects]; + subjectsCopy.forEach((subject) => { - subject.grades.sort((a, b) => b.givenAt.getTime() - a.givenAt.getTime()); + if (subject.grades) { + subject.grades.sort((a, b) => (b.givenAt?.getTime() ?? 0) - (a.givenAt?.getTime() ?? 0)); + } }); switch (sortMethod) { @@ -205,14 +211,14 @@ const GradesView: React.FC = () => { default: subjectsCopy.sort((a, b) => { - const aLatestGrade = a.grades[0]; - const bLatestGrade = b.grades[0]; + const aLatestGrade = a.grades?.[0]; + const bLatestGrade = b.grades?.[0]; if (!aLatestGrade && !bLatestGrade) { return 0; } if (!aLatestGrade) { return 1; } if (!bLatestGrade) { return -1; } - return bLatestGrade.givenAt.getTime() - aLatestGrade.givenAt.getTime(); + return (bLatestGrade.givenAt?.getTime() ?? 0) - (aLatestGrade.givenAt?.getTime() ?? 0); }); break; } @@ -221,32 +227,44 @@ const GradesView: React.FC = () => { }, [subjects, sortMethod]); const sortedGrades = useMemo(() => { - const gradesCopy = [...grades]; - gradesCopy.sort((a, b) => b.givenAt.getTime() - a.givenAt.getTime()); + const gradesCopy = [...grades].filter((g): g is any => g !== undefined && g.givenAt !== undefined); + gradesCopy.sort((a, b) => (b?.givenAt?.getTime() ?? 0) - (a?.givenAt?.getTime() ?? 0)); return gradesCopy; }, [grades]); // Search const [searchText, setSearchText] = useState(""); + const filteredSubjects = useMemo(() => { + // nothing searched if (searchText.trim() === "") { return sortedSubjects; } - const lowerSearchText = searchText.toLowerCase(); + const normalizedSearch = searchText.toLowerCase().trim(); return sortedSubjects.filter((subject) => { const subjectName = getSubjectName(subject.name).toLowerCase(); - if (subjectName.includes(lowerSearchText)) { + + if (subjectName.includes(normalizedSearch)) { return true; } - // Also search in grades descriptions - const matchingGrades = subject.grades.filter((grade) => { - return grade.description?.toLowerCase().includes(lowerSearchText); - }); + // Search in description, date and score + const hasMatchingGrade = subject.grades?.some((grade) => { + // description + const descriptionMatch = grade.description?.toLowerCase().includes(normalizedSearch); + + // date + const dateMatch = grade.givenAt?.toLocaleDateString(i18n.language).toLowerCase().includes(normalizedSearch); - return matchingGrades.length > 0; + // score + const scoreMatch = grade.studentScore?.value.toString().toLocaleLowerCase().includes(normalizedSearch); + + return descriptionMatch || dateMatch || scoreMatch; + }) ?? false; + + return hasMatchingGrade; }); }, [searchText, sortedSubjects]); @@ -263,6 +281,7 @@ const GradesView: React.FC = () => { const renderItem = useCallback(({ item }: { item: any }) => { const subject = item as Subject; + return ( // @ts-expect-error navigation types @@ -282,7 +301,7 @@ const GradesView: React.FC = () => { const ListHeader = useMemo(() => ((sortedGrades.length > 0 && searchText.length === 0) ? ( g !== undefined) as any} color={colors.primary} realAverage={serviceAverage || undefined} /> @@ -345,21 +364,22 @@ const GradesView: React.FC = () => { estimatedItemSize={210 + 12} showsHorizontalScrollIndicator={false} recycleItems={true} - keyExtractor={(item) => item.id} + keyExtractor={(item) => item?.id ?? ''} renderItem={({ item: grade }) => { + if (!grade) return; // @ts-expect-error navigation types navigation.navigate('(modals)/grade', { grade: grade, @@ -369,8 +389,8 @@ const GradesView: React.FC = () => { emoji: getSubjectEmoji(getSubjectById(grade.subjectId)?.name || ""), originalName: getSubjectById(grade.subjectId)?.name || "" }, - avgInfluence: getAvgInfluence(grade), - avgClass: getAvgClassInfluence(grade), + avgInfluence: grade ? getAvgInfluence(grade) : 0, + avgClass: grade ? getAvgClassInfluence(grade) : 0, }) }} /> From 0e16576071a18fb8374ec0fc920783b5bbf23547 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 10 Jan 2026 12:30:21 +0100 Subject: [PATCH 2/9] Update app/(tabs)/grades/index.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- app/(tabs)/grades/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index 481654bca..4b41ca9a0 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -227,7 +227,7 @@ const GradesView: React.FC = () => { }, [subjects, sortMethod]); const sortedGrades = useMemo(() => { - const gradesCopy = [...grades].filter((g): g is any => g !== undefined && g.givenAt !== undefined); + const gradesCopy = [...grades].filter(g => g !== undefined && g.givenAt !== undefined); gradesCopy.sort((a, b) => (b?.givenAt?.getTime() ?? 0) - (a?.givenAt?.getTime() ?? 0)); return gradesCopy; }, [grades]); From ff73d13e2fbd371fbab4655ec37c946b04e900c3 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 10 Jan 2026 12:37:55 +0100 Subject: [PATCH 3/9] Update app/(tabs)/grades/index.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- app/(tabs)/grades/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index 4b41ca9a0..218149aec 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -376,7 +376,7 @@ const GradesView: React.FC = () => { disabled={grade?.studentScore?.disabled} status={grade?.studentScore?.status} color={getSubjectColor(getSubjectById(grade?.subjectId ?? '')?.name || "")} - date={grade?.givenAt ?? new Date()} + date={grade?.givenAt} hasMaxScore={grade?.studentScore?.value === grade?.maxScore?.value && !grade?.studentScore?.disabled} onPress={() => { if (!grade) return; From ad69c6151ec8491b8261149e9eaf0b1ba95b75df Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 10 Jan 2026 12:38:38 +0100 Subject: [PATCH 4/9] Added suggested things --- app/(tabs)/grades/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index 4b41ca9a0..f9514d1b9 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -259,7 +259,7 @@ const GradesView: React.FC = () => { const dateMatch = grade.givenAt?.toLocaleDateString(i18n.language).toLowerCase().includes(normalizedSearch); // score - const scoreMatch = grade.studentScore?.value.toString().toLocaleLowerCase().includes(normalizedSearch); + const scoreMatch = grade.studentScore?.value.toString().toLowerCase().includes(normalizedSearch); return descriptionMatch || dateMatch || scoreMatch; }) ?? false; @@ -301,7 +301,7 @@ const GradesView: React.FC = () => { const ListHeader = useMemo(() => ((sortedGrades.length > 0 && searchText.length === 0) ? ( g !== undefined) as any} + grades={grades.filter((g): g is NonNullable => g !== undefined)} color={colors.primary} realAverage={serviceAverage || undefined} /> @@ -364,7 +364,7 @@ const GradesView: React.FC = () => { estimatedItemSize={210 + 12} showsHorizontalScrollIndicator={false} recycleItems={true} - keyExtractor={(item) => item?.id ?? ''} + keyExtractor={(item, index) => item?.id ?? `grade-${index}`} renderItem={({ item: grade }) => Date: Sat, 10 Jan 2026 12:39:30 +0100 Subject: [PATCH 5/9] Fixed error --- app/(tabs)/grades/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index cda46e01e..f9514d1b9 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -376,7 +376,7 @@ const GradesView: React.FC = () => { disabled={grade?.studentScore?.disabled} status={grade?.studentScore?.status} color={getSubjectColor(getSubjectById(grade?.subjectId ?? '')?.name || "")} - date={grade?.givenAt} + date={grade?.givenAt ?? new Date()} hasMaxScore={grade?.studentScore?.value === grade?.maxScore?.value && !grade?.studentScore?.disabled} onPress={() => { if (!grade) return; From efbada0e81de8f476634f5d7a9362ed3aec7f3c9 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 10 Jan 2026 14:43:52 +0100 Subject: [PATCH 6/9] Removed useless `toLowerCase()` and added fallback `false` to ensure boolean type --- app/(tabs)/grades/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index f9514d1b9..e2cd3b459 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -253,13 +253,13 @@ const GradesView: React.FC = () => { // Search in description, date and score const hasMatchingGrade = subject.grades?.some((grade) => { // description - const descriptionMatch = grade.description?.toLowerCase().includes(normalizedSearch); + const descriptionMatch = grade.description?.toLowerCase().includes(normalizedSearch) || false; // date - const dateMatch = grade.givenAt?.toLocaleDateString(i18n.language).toLowerCase().includes(normalizedSearch); + const dateMatch = grade.givenAt?.toLocaleDateString(i18n.language).includes(normalizedSearch) || false; // score - const scoreMatch = grade.studentScore?.value.toString().toLowerCase().includes(normalizedSearch); + const scoreMatch = grade.studentScore?.value.toString().includes(normalizedSearch) || false; return descriptionMatch || dateMatch || scoreMatch; }) ?? false; From b702d8b47f6962d640c915cf78cbde20f0765f01 Mon Sep 17 00:00:00 2001 From: LeChacal Date: Sat, 10 Jan 2026 15:19:02 +0100 Subject: [PATCH 7/9] Added date error handle TODO: **ADD TRANSLATION (Grade_Date_Error)** --- app/(tabs)/grades/index.tsx | 7 +++---- ui/components/CompactGrade.tsx | 22 +++++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index e2cd3b459..3c248684d 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -367,7 +367,6 @@ const GradesView: React.FC = () => { keyExtractor={(item, index) => item?.id ?? `grade-${index}`} renderItem={({ item: grade }) => { disabled={grade?.studentScore?.disabled} status={grade?.studentScore?.status} color={getSubjectColor(getSubjectById(grade?.subjectId ?? '')?.name || "")} - date={grade?.givenAt ?? new Date()} + date={grade?.givenAt} hasMaxScore={grade?.studentScore?.value === grade?.maxScore?.value && !grade?.studentScore?.disabled} onPress={() => { if (!grade) return; @@ -389,8 +388,8 @@ const GradesView: React.FC = () => { emoji: getSubjectEmoji(getSubjectById(grade.subjectId)?.name || ""), originalName: getSubjectById(grade.subjectId)?.name || "" }, - avgInfluence: grade ? getAvgInfluence(grade) : 0, - avgClass: grade ? getAvgClassInfluence(grade) : 0, + avgInfluence: getAvgInfluence(grade), + avgClass: getAvgClassInfluence(grade), }) }} /> diff --git a/ui/components/CompactGrade.tsx b/ui/components/CompactGrade.tsx index 941d464fd..5fa17b0f2 100644 --- a/ui/components/CompactGrade.tsx +++ b/ui/components/CompactGrade.tsx @@ -16,7 +16,7 @@ interface CompactGradeProps { description: string; score: number; outOf: number; - date: Date; + date: Date | undefined; disabled?: boolean; status?: string; onPress?: () => void, @@ -113,14 +113,22 @@ export const CompactGrade = ({ {capitalizeWords(title)} } - {date && - - {date.toLocaleDateString(i18n.language, { + + {/* Date or error message */} + + {date + ? date.toLocaleDateString(i18n.language, { day: "2-digit", month: "short", - })} - - } + }) + : t("Grade_Date_Error") + } + Date: Sat, 10 Jan 2026 15:26:45 +0100 Subject: [PATCH 8/9] Changed date error handling --- ui/components/CompactGrade.tsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/ui/components/CompactGrade.tsx b/ui/components/CompactGrade.tsx index 5fa17b0f2..6233dea44 100644 --- a/ui/components/CompactGrade.tsx +++ b/ui/components/CompactGrade.tsx @@ -16,7 +16,7 @@ interface CompactGradeProps { description: string; score: number; outOf: number; - date: Date | undefined; + date?: Date; disabled?: boolean; status?: string; onPress?: () => void, @@ -113,22 +113,14 @@ export const CompactGrade = ({ {capitalizeWords(title)} } - - {/* Date or error message */} - - {date - ? date.toLocaleDateString(i18n.language, { + {date && + + {date.toLocaleDateString(i18n.language, { day: "2-digit", month: "short", - }) - : t("Grade_Date_Error") - } - + })} + + } Date: Sat, 10 Jan 2026 15:39:50 +0100 Subject: [PATCH 9/9] Added const variables Added const variables for the subject `color`, `name`, `emoji`. One request not multiples. --- app/(tabs)/grades/index.tsx | 81 ++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index 3c248684d..cc90dd5b1 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -184,13 +184,14 @@ const GradesView: React.FC = () => { // Sort // Sort grades in subjects by date descending then sort subjects by latest grade descending const sortedSubjects = useMemo(() => { - const subjectsCopy = [...subjects]; - - subjectsCopy.forEach((subject) => { - if (subject.grades) { - subject.grades.sort((a, b) => (b.givenAt?.getTime() ?? 0) - (a.givenAt?.getTime() ?? 0)); - } - }); + const subjectsCopy = subjects.map((subject) => ({ + ...subject, + grades: subject.grades + ? [...subject.grades].sort( + (a, b) => (b.givenAt?.getTime() ?? 0) - (a.givenAt?.getTime() ?? 0) + ) + : subject.grades, + })); switch (sortMethod) { case "alphabetical": @@ -365,35 +366,43 @@ const GradesView: React.FC = () => { showsHorizontalScrollIndicator={false} recycleItems={true} keyExtractor={(item, index) => item?.id ?? `grade-${index}`} - renderItem={({ item: grade }) => - { - if (!grade) return; - // @ts-expect-error navigation types - navigation.navigate('(modals)/grade', { - grade: grade, - subjectInfo: { - name: getSubjectName(getSubjectById(grade.subjectId)?.name || ""), - color: getSubjectColor(getSubjectById(grade.subjectId)?.name || ""), - emoji: getSubjectEmoji(getSubjectById(grade.subjectId)?.name || ""), - originalName: getSubjectById(grade.subjectId)?.name || "" - }, - avgInfluence: getAvgInfluence(grade), - avgClass: getAvgClassInfluence(grade), - }) - }} - /> - } + renderItem={({ item: grade }) => { + const subject = getSubjectById(grade?.subjectId ?? ''); + const subjectName = subject?.name; + const safeEmoji = subjectName ? getSubjectEmoji(subjectName) : '🤓'; + const safeTitle = subjectName ? getSubjectName(subjectName) : ''; + const safeColor = subjectName ? getSubjectColor(subjectName) : '#888888'; + + return ( + { + if (!grade) return; + // @ts-expect-error navigation types + navigation.navigate('(modals)/grade', { + grade: grade, + subjectInfo: { + name: subjectName, + color: safeColor, + emoji: safeEmoji, + originalName: subjectName + }, + avgInfluence: getAvgInfluence(grade), + avgClass: getAvgClassInfluence(grade), + }) + }} + /> + ); + }} />