Skip to content
Open
91 changes: 58 additions & 33 deletions app/(tabs)/grades/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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;
}
Expand All @@ -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<string>("");

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]);

Expand All @@ -292,6 +310,7 @@ const GradesView: React.FC = () => {

const renderItem = useCallback(({ item }: { item: any }) => {
const subject = item as Subject;

return (
<ErrorBoundary>
{/* @ts-expect-error navigation types */}
Expand Down Expand Up @@ -378,38 +397,44 @@ const GradesView: React.FC = () => {
estimatedItemSize={210 + 12}
showsHorizontalScrollIndicator={false}
recycleItems={true}
keyExtractor={(item) => item.id}
renderItem={({ item: grade }) =>
<ErrorBoundary fallback={<View style={{ width: 140, height: 140 }} />}>
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 (
<CompactGrade
key={grade.id + "_compactGrade_header"}
emoji={getSubjectEmoji(getSubjectById(grade.subjectId)?.name || "")}
title={getSubjectName(getSubjectById(grade.subjectId)?.name || "")}
description={grade.description}
score={grade.studentScore?.value || 0}
outOf={grade.outOf?.value || 20}
disabled={grade.studentScore?.disabled}
status={grade.studentScore?.status}
color={getSubjectColor(getSubjectById(grade.subjectId)?.name || "")}
date={grade.givenAt}
emoji={safeEmoji}
title={safeTitle}
description={grade?.description || ""}
score={grade?.studentScore?.value || 0}
outOf={grade?.outOf?.value || 20}
disabled={grade?.studentScore?.disabled}
status={grade?.studentScore?.status}
color={safeColor}
date={grade?.givenAt}
hasMaxScore={grade?.studentScore?.value === grade?.maxScore?.value && !grade?.studentScore?.disabled}
onPress={() => {
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),
})
}}
/>
</ErrorBoundary>
}
);
}}
/>
</Stack>
</Dynamic>
Expand Down
2 changes: 1 addition & 1 deletion ui/components/CompactGrade.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface CompactGradeProps {
description: string;
score: number;
outOf: number;
date: Date;
date?: Date;
disabled?: boolean;
status?: string;
onPress?: () => void,
Expand Down