diff --git a/app/(tabs)/grades/index.tsx b/app/(tabs)/grades/index.tsx index 201def29..d481e101 100644 --- a/app/(tabs)/grades/index.tsx +++ b/app/(tabs)/grades/index.tsx @@ -170,7 +170,9 @@ const GradesView: React.FC = () => { setGradesLoading(false); return; } + setSubjects(grades.subjects); + if (grades.studentOverall && typeof grades.studentOverall.value === 'number') { setServiceAverage(grades.studentOverall.value) } else { @@ -210,10 +212,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) => { - subject.grades.sort((a, b) => b.givenAt.getTime() - a.givenAt.getTime()); - }); + 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": @@ -241,7 +247,7 @@ const GradesView: React.FC = () => { 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; } @@ -250,32 +256,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 !== 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) || false; - return (matchingGrades?.length || 0) > 0; + // date + const dateMatch = grade.givenAt?.toLocaleDateString(i18n.language).includes(normalizedSearch) || false; + + // score + const scoreMatch = grade.studentScore?.value.toString().includes(normalizedSearch) || false; + + return descriptionMatch || dateMatch || scoreMatch; + }) ?? false; + + return hasMatchingGrade; }); }, [searchText, sortedSubjects]); @@ -292,6 +310,7 @@ const GradesView: React.FC = () => { const renderItem = useCallback(({ item }: { item: any }) => { const subject = item as Subject; + return ( {/* @ts-expect-error navigation types */} @@ -378,38 +397,44 @@ const GradesView: React.FC = () => { estimatedItemSize={210 + 12} showsHorizontalScrollIndicator={false} recycleItems={true} - keyExtractor={(item) => item.id} - renderItem={({ item: grade }) => - }> + keyExtractor={(item, index) => item?.id ?? `grade-${index}`} + 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: getSubjectName(getSubjectById(grade.subjectId)?.name || ""), - color: getSubjectColor(getSubjectById(grade.subjectId)?.name || ""), - emoji: getSubjectEmoji(getSubjectById(grade.subjectId)?.name || ""), - originalName: getSubjectById(grade.subjectId)?.name || "" + name: subjectName, + color: safeColor, + emoji: safeEmoji, + originalName: subjectName }, avgInfluence: getAvgInfluence(grade), avgClass: getAvgClassInfluence(grade), }) }} /> - - } + ); + }} /> diff --git a/ui/components/CompactGrade.tsx b/ui/components/CompactGrade.tsx index 941d464f..6233dea4 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; disabled?: boolean; status?: string; onPress?: () => void,