@@ -5,19 +5,12 @@ import WelcomeBanner from './WelcomeBanner';
55import AssignmentManagement from './AssignmentManagement' ;
66import OptimizedDashboardService from '../../utils/optimizedDashboardService' ;
77import ProgressService from '../../utils/progressService' ;
8+ import ResourceLoadingIndicator , { StatisticLoader , ResourceProgressBadge } from '../common/ResourceLoadingIndicator' ;
89
910// OPTIMIZATION 1: Memoized sub-components to prevent unnecessary re-renders
1011const MemoizedWelcomeBanner = React . memo ( WelcomeBanner ) ;
1112const MemoizedAssignmentManagement = React . memo ( AssignmentManagement ) ;
1213
13- // Small loading component for statistics
14- const StatisticLoader = ( { color = 'gray' } ) => (
15- < div className = "flex items-center" >
16- < div className = { `w-3 h-3 animate-spin rounded-full border border-${ color } -300 border-t-${ color } -600` } > </ div >
17- < span className = "ml-1 text-gray-400" > ...</ span >
18- </ div >
19- ) ;
20-
2114const LearningProgressSection = ( { user } ) => {
2215 const navigate = useNavigate ( ) ;
2316
@@ -35,10 +28,12 @@ const LearningProgressSection = ({ user }) => {
3528 const [ leagueStatistics , setLeagueStatistics ] = useState ( { } ) ;
3629 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
3730 const [ isSearchActive , setIsSearchActive ] = useState ( false ) ;
38- const [ statisticsLoading , setStatisticsLoading ] = useState ( true ) ;
39- const [ resourcesCalculationComplete , setResourcesCalculationComplete ] = useState ( false ) ;
40- const [ completedResourceCalculations , setCompletedResourceCalculations ] = useState ( new Set ( ) ) ;
41- const [ totalLeaguesForCalculation , setTotalLeaguesForCalculation ] = useState ( 0 ) ;
31+
32+ // IMPROVEMENT: Separate resource calculation state from main loading state
33+ const [ resourceCalculationsInProgress , setResourceCalculationsInProgress ] = useState ( new Set ( ) ) ;
34+ const [ resourceCalculationsCompleted , setResourceCalculationsCompleted ] = useState ( new Set ( ) ) ;
35+ const [ totalResourceCalculations , setTotalResourceCalculations ] = useState ( 0 ) ;
36+ const [ showResourcesCompleteToast , setShowResourcesCompleteToast ] = useState ( false ) ;
4237
4338 // OPTIMIZATION 3: Progressive data loading with immediate UI feedback
4439 const loadDashboardDataOptimized = useCallback ( async ( ) => {
@@ -56,10 +51,13 @@ const LearningProgressSection = ({ user }) => {
5651
5752 // Set basic league statistics immediately for instant display
5853 if ( data . basicLeagueStats ) {
54+ console . log ( 'Setting basic league statistics:' , data . basicLeagueStats ) ;
5955 setLeagueStatistics ( data . basicLeagueStats ) ;
60- setStatisticsLoading ( false ) ;
6156 }
6257
58+ // IMPROVEMENT: Main loading is complete - show dashboard immediately
59+ setLoading ( false ) ;
60+
6361 // Load resource progress in background for enrolled leagues
6462 if ( data . dashboardData ?. enrollments ?. length > 0 ) {
6563 OptimizedDashboardService . loadResourceProgressOptimized ( data . dashboardData )
@@ -72,57 +70,77 @@ const LearningProgressSection = ({ user }) => {
7270
7371 // Calculate statistics immediately for display, then update in background
7472 if ( data . leagues ?. length > 0 ) {
75- setTotalLeaguesForCalculation ( data . leagues . length ) ;
76- console . log ( `Starting resource calculations for ${ data . leagues . length } leagues:` , data . leagues . map ( l => l . id ) ) ;
73+ // IMPROVEMENT: Check which leagues already have complete resource calculations
74+ const leaguesNeedingCalculation = data . leagues . filter ( league => {
75+ const cachedStats = data . basicLeagueStats [ league . id ] ;
76+ // Only calculate if no cached stats or resource count is missing/zero
77+ return ! cachedStats || cachedStats . resourcesCount === 0 ;
78+ } ) ;
7779
78- // Create callback to update resource counts when background calculation completes
79- const handleResourceUpdate = ( leagueId , resourceCount ) => {
80- console . log ( `Resource calculation completed for league ${ leagueId } : ${ resourceCount } resources` ) ;
81-
82- setLeagueStatistics ( prevStats => ( {
83- ...prevStats ,
84- [ leagueId ] : {
85- ...prevStats [ leagueId ] ,
86- resourcesCount : resourceCount
87- }
88- } ) ) ;
80+ setTotalResourceCalculations ( leaguesNeedingCalculation . length ) ;
81+ setResourceCalculationsInProgress ( new Set ( leaguesNeedingCalculation . map ( l => l . id ) ) ) ;
82+
83+ if ( leaguesNeedingCalculation . length > 0 ) {
84+ console . log ( `Starting resource calculations for ${ leaguesNeedingCalculation . length } leagues:` , leaguesNeedingCalculation . map ( l => l . id ) ) ;
8985
90- // Track completion of each league's resource calculation
91- setCompletedResourceCalculations ( prevCompleted => {
92- const newCompleted = new Set ( [ ...prevCompleted , leagueId ] ) ;
93- console . log ( `Completed resource calculations: ${ newCompleted . size } /${ data . leagues . length } ` , [ ...newCompleted ] ) ;
94-
95- // Check if all resource calculations are complete
96- if ( newCompleted . size === data . leagues . length ) {
97- console . log ( 'All resource calculations completed!' ) ;
98- setResourcesCalculationComplete ( true ) ;
99- setStatisticsLoading ( false ) ;
100- }
86+ // Create callback to update resource counts when background calculation completes
87+ const handleResourceUpdate = ( leagueId , resourceCount ) => {
88+ console . log ( `Resource calculation completed for league ${ leagueId } : ${ resourceCount } resources` ) ;
10189
102- return newCompleted ;
103- } ) ;
104- } ;
105-
106- // Calculate accurate statistics in background to update basic stats
107- OptimizedDashboardService . calculateAllLeagueStatistics ( data . leagues , handleResourceUpdate )
108- . then ( accurateStats => {
109- console . log ( 'Initial statistics calculated:' , accurateStats ) ;
11090 setLeagueStatistics ( prevStats => ( {
11191 ...prevStats ,
112- ...accurateStats
92+ [ leagueId ] : {
93+ ...prevStats [ leagueId ] ,
94+ resourcesCount : resourceCount
95+ }
11396 } ) ) ;
11497
115- // Note: We don't set statisticsLoading to false here anymore
116- // It will be set to false only when all resource calculations complete via the callback
117- } ) ;
98+ // Track completion of each league's resource calculation
99+ setResourceCalculationsCompleted ( prevCompleted => {
100+ const newCompleted = new Set ( [ ...prevCompleted , leagueId ] ) ;
101+ console . log ( `Completed resource calculations: ${ newCompleted . size } /${ leaguesNeedingCalculation . length } ` , [ ...newCompleted ] ) ;
102+
103+ // Check if all resource calculations are complete
104+ if ( newCompleted . size === leaguesNeedingCalculation . length ) {
105+ console . log ( 'All resource calculations completed!' ) ;
106+ setShowResourcesCompleteToast ( true ) ;
107+ // Hide the toast after 4 seconds
108+ setTimeout ( ( ) => {
109+ setShowResourcesCompleteToast ( false ) ;
110+ } , 4000 ) ;
111+ }
112+
113+ return newCompleted ;
114+ } ) ;
115+
116+ // Remove from in-progress set
117+ setResourceCalculationsInProgress ( prevInProgress => {
118+ const newInProgress = new Set ( prevInProgress ) ;
119+ newInProgress . delete ( leagueId ) ;
120+ return newInProgress ;
121+ } ) ;
122+ } ;
123+
124+ // Calculate accurate statistics in background to update basic stats
125+ OptimizedDashboardService . calculateAllLeagueStatistics ( leaguesNeedingCalculation , handleResourceUpdate )
126+ . then ( accurateStats => {
127+ console . log ( 'Initial statistics calculated:' , accurateStats ) ;
128+ setLeagueStatistics ( prevStats => ( {
129+ ...prevStats ,
130+ ...accurateStats
131+ } ) ) ;
132+ } ) ;
133+ } else {
134+ console . log ( 'All leagues already have complete resource calculations' ) ;
135+ // All leagues already have complete data, no calculations needed
136+ setTotalResourceCalculations ( 0 ) ;
137+ setResourceCalculationsInProgress ( new Set ( ) ) ;
138+ }
118139 } else {
119140 console . log ( 'No leagues found, marking calculations as complete' ) ;
120- setStatisticsLoading ( false ) ;
121- setResourcesCalculationComplete ( true ) ;
141+ setTotalResourceCalculations ( 0 ) ;
122142 }
123143
124- // Only hide initial loading, but keep showing progress for resource calculations
125- setLoading ( false ) ;
126144 } catch ( err ) {
127145 console . error ( 'Error loading dashboard data:' , err ) ;
128146 setError ( `Failed to connect to the learning platform. Please try again later. (${ err . message } )` ) ;
@@ -199,31 +217,14 @@ const LearningProgressSection = ({ user }) => {
199217 ) ;
200218 } , [ searchTerm , isSearchActive ] ) ;
201219
202- const getLoadingMessage = ( ) => {
203- if ( loading ) return 'Loading dashboard...' ;
204- if ( ! resourcesCalculationComplete && totalLeaguesForCalculation > 0 ) {
205- return `Loading Dashboard... (${ completedResourceCalculations . size } /${ totalLeaguesForCalculation } )` ;
206- }
207- return 'Almost ready!' ;
208- } ;
209-
210- // Show loading until both basic loading and resource calculations are complete
211- const isFullyLoaded = ! loading && resourcesCalculationComplete ;
212-
213- if ( ! isFullyLoaded ) {
214- const loadingMessage = getLoadingMessage ( ) ;
220+ // IMPROVEMENT: Show loading skeleton only for basic dashboard loading, not resource calculations
221+ // This allows users to see and interact with the dashboard immediately while resources load in background
222+ if ( loading ) {
215223
216224 return (
217225 < div className = "min-h-screen bg-gray-50" >
218226 < div className = "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6" >
219227 < div className = "space-y-6" >
220- { /* Minimal Loading Text */ }
221- < div className = "bg-white rounded-2xl border border-gray-100 p-4" >
222- < div className = "text-center" >
223- < h3 className = "text-lg font-medium text-gray-900" > { loadingMessage } </ h3 >
224- </ div >
225- </ div >
226-
227228 { /* Header skeleton */ }
228229 < div className = "bg-white rounded-2xl border border-gray-100 p-6 space-y-4" >
229230 < div className = "h-8 bg-gray-200 rounded w-1/3 animate-pulse" > </ div >
@@ -317,6 +318,16 @@ const LearningProgressSection = ({ user }) => {
317318 </ div >
318319 ) }
319320 </ div >
321+
322+ { /* IMPROVEMENT: Global Resource Calculation Progress */ }
323+ { resourceCalculationsInProgress . size > 0 && (
324+ < ResourceLoadingIndicator
325+ isLoading = { true }
326+ completedCount = { resourceCalculationsCompleted . size }
327+ totalCount = { totalResourceCalculations }
328+ compact = { false }
329+ />
330+ ) }
320331
321332 { /* Welcome Banner for New Users */ }
322333 { ( ! dashboardData ?. enrollments || dashboardData . enrollments . length === 0 ) && (
@@ -666,11 +677,9 @@ const LearningProgressSection = ({ user }) => {
666677 const leagueName = league . name || 'Learning League' ;
667678 const leagueDescription = league . description || 'A comprehensive learning journey designed to build your skills.' ;
668679
669- // Determine if we should show loading indicators
670- // Show loading when statistics are being calculated AND we don't have dynamic stats yet
671- const showWeeksLoading = statisticsLoading && ! dynamicStats ;
672- const showSectionsLoading = statisticsLoading && ! dynamicStats ;
673- const showResourcesLoading = statisticsLoading && ! dynamicStats ;
680+ // IMPROVEMENT: Check if this specific league's resources are still being calculated
681+ const isCalculatingResources = resourceCalculationsInProgress . has ( league . id ) ;
682+ const showResourcesLoading = isCalculatingResources ;
674683
675684 return (
676685 < div
@@ -686,6 +695,15 @@ const LearningProgressSection = ({ user }) => {
686695 }
687696 } }
688697 >
698+ { /* IMPROVEMENT: Add resource calculation progress badge */ }
699+ { isCalculatingResources && (
700+ < ResourceProgressBadge
701+ isCalculating = { true }
702+ completedCount = { resourceCalculationsCompleted . size }
703+ totalCount = { totalResourceCalculations }
704+ />
705+ ) }
706+
689707 < div className = "p-5" >
690708 < div className = "flex items-start justify-between" >
691709 < div className = "flex-1" >
@@ -698,19 +716,11 @@ const LearningProgressSection = ({ user }) => {
698716 < div className = "flex items-center space-x-2 text-xs text-gray-500" >
699717 < span className = "flex items-center" >
700718 < div className = "w-1.5 h-1.5 bg-blue-400 rounded-full mr-1" > </ div >
701- { showWeeksLoading ? (
702- < StatisticLoader color = "blue" />
703- ) : (
704- `${ weeksCount } ${ weeksCount === 1 ? 'week' : 'weeks' } `
705- ) }
719+ { `${ weeksCount } ${ weeksCount === 1 ? 'week' : 'weeks' } ` }
706720 </ span >
707721 < span className = "flex items-center" >
708722 < div className = "w-1.5 h-1.5 bg-green-400 rounded-full mr-1" > </ div >
709- { showSectionsLoading ? (
710- < StatisticLoader color = "green" />
711- ) : (
712- `${ sectionsCount } ${ sectionsCount === 1 ? 'section' : 'sections' } `
713- ) }
723+ { `${ sectionsCount } ${ sectionsCount === 1 ? 'section' : 'sections' } ` }
714724 </ span >
715725 < span className = "flex items-center" >
716726 < div className = "w-1.5 h-1.5 bg-purple-400 rounded-full mr-1" > </ div >
@@ -809,6 +819,33 @@ const LearningProgressSection = ({ user }) => {
809819 { /* Bottom spacer */ }
810820 < div className = "h-6" > </ div >
811821 </ div >
822+
823+ { /* IMPROVEMENT: Resource Calculations Complete Toast */ }
824+ { showResourcesCompleteToast && (
825+ < div className = "fixed top-4 right-4 z-50 animate-slide-in-right" >
826+ < div className = "bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-3 max-w-sm" >
827+ < div className = "flex-shrink-0" >
828+ < div className = "w-6 h-6 bg-white rounded-full flex items-center justify-center" >
829+ < svg className = "w-4 h-4 text-green-500" fill = "currentColor" viewBox = "0 0 20 20" >
830+ < path fillRule = "evenodd" d = "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule = "evenodd" />
831+ </ svg >
832+ </ div >
833+ </ div >
834+ < div className = "flex-1" >
835+ < p className = "text-sm font-medium" > All resources loaded! 🎉</ p >
836+ < p className = "text-xs text-green-100" > Your dashboard is now fully updated</ p >
837+ </ div >
838+ < button
839+ onClick = { ( ) => setShowResourcesCompleteToast ( false ) }
840+ className = "flex-shrink-0 text-white/80 hover:text-white transition-colors"
841+ >
842+ < svg className = "w-4 h-4" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
843+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M6 18L18 6M6 6l12 12" />
844+ </ svg >
845+ </ button >
846+ </ div >
847+ </ div >
848+ ) }
812849 </ div >
813850 ) ;
814851} ;
0 commit comments