From 8fc1dbd9ef731535fd3d1cec0a3931cd418757d4 Mon Sep 17 00:00:00 2001 From: Sangaran Ramesch Date: Fri, 16 Jan 2026 21:07:09 +0100 Subject: [PATCH 1/3] adjust frontend to skillValue and skillAllUsersStats --- app/courses/[courseId]/progress/page.tsx | 99 +++++++++++++++++++----- src/schema.graphql | 3 +- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/app/courses/[courseId]/progress/page.tsx b/app/courses/[courseId]/progress/page.tsx index 41e31b7e..a1b52906 100644 --- a/app/courses/[courseId]/progress/page.tsx +++ b/app/courses/[courseId]/progress/page.tsx @@ -62,11 +62,12 @@ export default function LearningProgress() { skills { skillName skillCategory - skillValue + skillValue { + skillValue + } skillAllUsersStats { skillValueSum participantCount - averageSkillValue } } } @@ -94,6 +95,61 @@ export default function LearningProgress() { const [showAverageProgress, setAverageProgress] = useState(false); + type skillItem = { + skillValue: number; + skillAverageValue: number; + maxParticipantCount: number; + }; + + const getCategorySkillKey = (category: string, skillName: string) => { + return `${category}_${skillName}`; + } + + const progressBySkill = useMemo(() => { + const progressBySkillValues = new Map(); + + uniqueCategories.forEach((category) => { + skillsByCategory[category].forEach((skill) => { + const key = getCategorySkillKey(category, skill.skillName); + + if (!progressBySkillValues.has(key)) { + progressBySkillValues.set(key, { + progressSum: 0, + averageProgressSum: 0, + count: 0, + maxParticipantCount: 0, + }); + } + + const progressItem = progressBySkillValues.get(key)!; + + progressItem.progressSum += skill.skillValue.skillValue; + progressItem.averageProgressSum += skill.skillAllUsersStats.skillValueSum; + progressItem.count++; + progressItem.maxParticipantCount = Math.max( + progressItem.maxParticipantCount, + skill.skillAllUsersStats.participantCount + ); + }); + }); + + const result = new Map(); + progressBySkillValues.forEach(({ progressSum: sum, averageProgressSum: averageSum, count, maxParticipantCount }, key) => { + result.set(key, { + skillValue: sum / count, + skillAverageValue: (averageSum / count) / course.numberOfCourseMemberships, + maxParticipantCount, + }); + }); + + return result; + }, [course.numberOfCourseMemberships, skillsByCategory, uniqueCategories]); + const sortedCategories = useMemo(() => { if (uniqueCategories.length === 0) return []; return [...uniqueCategories].sort((a, b) => { @@ -104,14 +160,14 @@ export default function LearningProgress() { ).values() ); const progressSum = uniqueSkillsInCategory.reduce((sum, skill) => { - const progress = skill.skillValue; + const progress = progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.skillValue ?? 0; return sum + progress; }, 0); return (progressSum / uniqueSkillsInCategory.length) * 100; }; return getTotalProgress(b) - getTotalProgress(a); }); - }, [skillsByCategory, uniqueCategories]); + }, [progressBySkill, skillsByCategory, uniqueCategories]); const currentUniqueSkills = useMemo(() => { if (sortedCategories.length === 0) return []; @@ -123,9 +179,11 @@ export default function LearningProgress() { return acc; }, [] as (typeof skillsByCategory)[string]) .sort((skillA, skillB) => { - return skillB.skillValue - skillA.skillValue; + const progressA = progressBySkill.get(getCategorySkillKey(sortedCategories[selectedCategory], skillA.skillName))?.skillValue ?? 0; + const progressB = progressBySkill.get(getCategorySkillKey(sortedCategories[selectedCategory], skillB.skillName))?.skillValue ?? 0; + return progressB - progressA; }); - }, [selectedCategory, skillsByCategory, sortedCategories]); + }, [progressBySkill, selectedCategory, skillsByCategory, sortedCategories]); const urgentChapters = useMemo(() => { return course.chapters.elements.filter((chapter) => { @@ -172,11 +230,12 @@ export default function LearningProgress() { const tempMap = new Map(); course.skills.forEach((skill) => { - tempMap.set(skill.skillName, skill.skillValue * 100); + const key = getCategorySkillKey(skill.skillCategory, skill.skillName); + tempMap.set(key, (progressBySkill.get(key)?.skillValue ?? 0) * 100); }); sessionStorage.setItem("previousProgress", JSON.stringify([...tempMap])); - }, [course.skills, uniqueCategories.length]); + }, [course.skills, progressBySkill, uniqueCategories.length]); const theme = useTheme(); @@ -257,14 +316,14 @@ export default function LearningProgress() { ); const progressSum = uniqueSkillsInCategory.reduce((sum, skill) => { - const progress = skill.skillValue * 100; + const progress = (progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.skillValue ?? 0) * 100; return sum + progress; }, 0); const averageProgressSum = uniqueSkillsInCategory.reduce( (sum, skill) => { const averageProgress = - skill.skillAllUsersStats.averageSkillValue * 100; + (progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.skillAverageValue ?? 0) * 100; return sum + averageProgress; }, 0 @@ -272,18 +331,19 @@ export default function LearningProgress() { const maxParticipantCountForaSkill = Math.max( ...uniqueSkillsInCategory.map( - (skill) => skill.skillAllUsersStats.participantCount + (skill) => progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.maxParticipantCount ?? 0 ) ); const categoryProgressValue = progressSum / uniqueSkillsInCategory.length; + const categoryAverageProgressValue = averageProgressSum / uniqueSkillsInCategory.length; const tempSumPreviousProgress = uniqueSkillsInCategory.reduce( (sum, skill) => - sum + (previousProgress.get(skill.skillName) ?? 0), + sum + (previousProgress.get(getCategorySkillKey(category, skill.skillName)) ?? 0), 0 ); @@ -381,12 +441,17 @@ export default function LearningProgress() { ) ); - const skillProgressValue = currentSkill.skillValue * 100; + const key = getCategorySkillKey(currentSkill.skillCategory, currentSkill.skillName); + + const skillProgressValue = (progressBySkill.get(key)?.skillValue ?? 0) * 100; + const skillAverageProgressValue = - currentSkill.skillAllUsersStats.averageSkillValue * 100; + (progressBySkill.get(key)?.skillAverageValue ?? 0) * 100; + + const maxParticipantCount = progressBySkill.get(key)?.maxParticipantCount ?? 0; const previousSkillProgressValue = - previousProgress.get(currentSkill.skillName) ?? + previousProgress.get(key) ?? skillProgressValue; return ( @@ -420,9 +485,7 @@ export default function LearningProgress() { } isUrgent={urgent} showAverageProgress={showAverageProgress} - participantCount={ - currentSkill.skillAllUsersStats.participantCount - } + participantCount={maxParticipantCount} courseMemberCount={course.numberOfCourseMemberships} openTaskCount={ filteredSuggestionsBySkill(currentSkill.skillName).length diff --git a/src/schema.graphql b/src/schema.graphql index 58904921..4107a868 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -1608,11 +1608,10 @@ type Skill { skillCategory: String! skillLevels: SkillLevels skillName: String! - skillValue: Float! + skillValue: SkillValue! } type SkillAllUsersStats { - averageSkillValue: Float! participantCount: Int! skillId: UUID! skillValueSum: Float! From 32c8c4a32ad473491c06bc420c79a55befda02ab Mon Sep 17 00:00:00 2001 From: Sangaran Ramesch Date: Fri, 16 Jan 2026 21:17:31 +0100 Subject: [PATCH 2/3] style fix --- app/courses/[courseId]/progress/page.tsx | 100 ++++++++++++++++------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/app/courses/[courseId]/progress/page.tsx b/app/courses/[courseId]/progress/page.tsx index a1b52906..89dda90e 100644 --- a/app/courses/[courseId]/progress/page.tsx +++ b/app/courses/[courseId]/progress/page.tsx @@ -103,15 +103,18 @@ export default function LearningProgress() { const getCategorySkillKey = (category: string, skillName: string) => { return `${category}_${skillName}`; - } + }; const progressBySkill = useMemo(() => { - const progressBySkillValues = new Map(); + const progressBySkillValues = new Map< + string, + { + progressSum: number; + averageProgressSum: number; + count: number; + maxParticipantCount: number; + } + >(); uniqueCategories.forEach((category) => { skillsByCategory[category].forEach((skill) => { @@ -129,7 +132,8 @@ export default function LearningProgress() { const progressItem = progressBySkillValues.get(key)!; progressItem.progressSum += skill.skillValue.skillValue; - progressItem.averageProgressSum += skill.skillAllUsersStats.skillValueSum; + progressItem.averageProgressSum += + skill.skillAllUsersStats.skillValueSum; progressItem.count++; progressItem.maxParticipantCount = Math.max( progressItem.maxParticipantCount, @@ -139,13 +143,24 @@ export default function LearningProgress() { }); const result = new Map(); - progressBySkillValues.forEach(({ progressSum: sum, averageProgressSum: averageSum, count, maxParticipantCount }, key) => { - result.set(key, { - skillValue: sum / count, - skillAverageValue: (averageSum / count) / course.numberOfCourseMemberships, - maxParticipantCount, - }); - }); + progressBySkillValues.forEach( + ( + { + progressSum: sum, + averageProgressSum: averageSum, + count, + maxParticipantCount, + }, + key + ) => { + result.set(key, { + skillValue: sum / count, + skillAverageValue: + averageSum / count / course.numberOfCourseMemberships, + maxParticipantCount, + }); + } + ); return result; }, [course.numberOfCourseMemberships, skillsByCategory, uniqueCategories]); @@ -160,7 +175,9 @@ export default function LearningProgress() { ).values() ); const progressSum = uniqueSkillsInCategory.reduce((sum, skill) => { - const progress = progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.skillValue ?? 0; + const progress = + progressBySkill.get(getCategorySkillKey(category, skill.skillName)) + ?.skillValue ?? 0; return sum + progress; }, 0); return (progressSum / uniqueSkillsInCategory.length) * 100; @@ -179,8 +196,20 @@ export default function LearningProgress() { return acc; }, [] as (typeof skillsByCategory)[string]) .sort((skillA, skillB) => { - const progressA = progressBySkill.get(getCategorySkillKey(sortedCategories[selectedCategory], skillA.skillName))?.skillValue ?? 0; - const progressB = progressBySkill.get(getCategorySkillKey(sortedCategories[selectedCategory], skillB.skillName))?.skillValue ?? 0; + const progressA = + progressBySkill.get( + getCategorySkillKey( + sortedCategories[selectedCategory], + skillA.skillName + ) + )?.skillValue ?? 0; + const progressB = + progressBySkill.get( + getCategorySkillKey( + sortedCategories[selectedCategory], + skillB.skillName + ) + )?.skillValue ?? 0; return progressB - progressA; }); }, [progressBySkill, selectedCategory, skillsByCategory, sortedCategories]); @@ -231,7 +260,7 @@ export default function LearningProgress() { course.skills.forEach((skill) => { const key = getCategorySkillKey(skill.skillCategory, skill.skillName); - tempMap.set(key, (progressBySkill.get(key)?.skillValue ?? 0) * 100); + tempMap.set(key, (progressBySkill.get(key)?.skillValue ?? 0) * 100); }); sessionStorage.setItem("previousProgress", JSON.stringify([...tempMap])); @@ -316,14 +345,19 @@ export default function LearningProgress() { ); const progressSum = uniqueSkillsInCategory.reduce((sum, skill) => { - const progress = (progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.skillValue ?? 0) * 100; + const progress = + (progressBySkill.get( + getCategorySkillKey(category, skill.skillName) + )?.skillValue ?? 0) * 100; return sum + progress; }, 0); const averageProgressSum = uniqueSkillsInCategory.reduce( (sum, skill) => { const averageProgress = - (progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.skillAverageValue ?? 0) * 100; + (progressBySkill.get( + getCategorySkillKey(category, skill.skillName) + )?.skillAverageValue ?? 0) * 100; return sum + averageProgress; }, 0 @@ -331,7 +365,10 @@ export default function LearningProgress() { const maxParticipantCountForaSkill = Math.max( ...uniqueSkillsInCategory.map( - (skill) => progressBySkill.get(getCategorySkillKey(category, skill.skillName))?.maxParticipantCount ?? 0 + (skill) => + progressBySkill.get( + getCategorySkillKey(category, skill.skillName) + )?.maxParticipantCount ?? 0 ) ); @@ -343,7 +380,10 @@ export default function LearningProgress() { const tempSumPreviousProgress = uniqueSkillsInCategory.reduce( (sum, skill) => - sum + (previousProgress.get(getCategorySkillKey(category, skill.skillName)) ?? 0), + sum + + (previousProgress.get( + getCategorySkillKey(category, skill.skillName) + ) ?? 0), 0 ); @@ -441,18 +481,22 @@ export default function LearningProgress() { ) ); - const key = getCategorySkillKey(currentSkill.skillCategory, currentSkill.skillName); + const key = getCategorySkillKey( + currentSkill.skillCategory, + currentSkill.skillName + ); - const skillProgressValue = (progressBySkill.get(key)?.skillValue ?? 0) * 100; + const skillProgressValue = + (progressBySkill.get(key)?.skillValue ?? 0) * 100; const skillAverageProgressValue = (progressBySkill.get(key)?.skillAverageValue ?? 0) * 100; - const maxParticipantCount = progressBySkill.get(key)?.maxParticipantCount ?? 0; + const maxParticipantCount = + progressBySkill.get(key)?.maxParticipantCount ?? 0; const previousSkillProgressValue = - previousProgress.get(key) ?? - skillProgressValue; + previousProgress.get(key) ?? skillProgressValue; return ( Date: Sat, 17 Jan 2026 20:56:43 +0100 Subject: [PATCH 3/3] schema update --- src/schema.graphql | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/schema.graphql b/src/schema.graphql index 12433f54..2f7d711f 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -1342,6 +1342,7 @@ type Query { inventoryForUser: Inventory! isAccessTokenAvailable(provider: ExternalServiceProviderDto!): Boolean! itemsByUserId(userId: UUID!): [UserItem!]! + latestProactiveFeedback: String mediaRecords: [MediaRecord!]! @deprecated(reason: "In production there should probably be no way to get all media records of the system.") mediaRecordsByContentIds(contentIds: [UUID!]!): [[MediaRecord!]!]! mediaRecordsByIds(ids: [UUID!]!): [MediaRecord!]! @@ -1360,12 +1361,6 @@ type Query { thread(id: UUID!): Thread threadsByContentId(id: UUID!): [Thread!]! tutorImmersiveWidgetSpeechContent(courseId: UUID!): String! - """ - Retrieves the latest proactive feedback for the current user (any assessment). - The feedback is deleted after retrieval and must be less than 30 minutes old. - Returns the feedback text or null if no feedback is available. - """ - latestProactiveFeedback: String userCourseRewardScores(courseId: UUID!): RewardScores! userMediaRecords: [MediaRecord!]! }