-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLearningPath.tsx
More file actions
197 lines (169 loc) · 7.51 KB
/
Copy pathLearningPath.tsx
File metadata and controls
197 lines (169 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// pages/LearningPathPage/LearningPathPage.tsx
import { useEffect, useRef, useState } from 'react';
import { useAppDispatch, useAppSelector } from "@app/hooks";
import { setStickyHeaderId } from "@features/LearningPath/store/slices/ui.slice";
import { selectStickyHeaderId } from "@features/LearningPath/store/selectors/ui.selector";
import CourseSwitcher from "@features/LearningPath/components/CourseSwitcher";
import UnitPath from "@features/LearningPath/components/UnitPath";
import CourseCompletionSection from "@features/LearningPath/components/CourseCompletionSection";
import PracticeButton from "@features/LearningPath/components/PracticeButton";
import ScrollToTopButton from "@features/LearningPath/components/ScrollToTopButton";
import {useGetCourseSubtopicsQuery} from "@core/services/learningPath/knowledge-graph-api";
/**
* LearningPathPage - Main entry point for the learning path interface
* Now uses the enrolled courses and course subtopics APIs
*/
export const LearningPathPage: React.FC = () => {
const dispatch = useAppDispatch();
// ============================================
// LOCAL STATE
// ============================================
const [selectedCourseId, setSelectedCourseId] = useState<string | null>(null);
const [selectedTopicId, setSelectedTopicId] = useState<string | null>(null);
const [scrollY, setScrollY] = useState(0);
const unitRefs = useRef<(HTMLDivElement | null)[]>([]);
// ============================================
// REDUX STATE
// ============================================
const stickyHeaderId = useAppSelector(selectStickyHeaderId);
// ============================================
// API QUERIES
// ============================================
// Fetch subtopics for selected course
const {
data: subtopicsData,
isLoading: subtopicsLoading,
error: subtopicsError,
} = useGetCourseSubtopicsQuery(selectedCourseId || "", {
skip: !selectedCourseId,
});
const subtopics = subtopicsData?.subtopics || [];
const progress = subtopicsData?.progress;
// ============================================
// EFFECTS - Scroll Handling
// ============================================
// Handle scroll events for sticky headers and scroll-to-top button
useEffect(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
setScrollY(currentScrollY);
// Determine which subtopic header should be sticky
const scrollPosition = currentScrollY + 80; // Account for padding
if (subtopics.length > 0) {
for (let i = subtopics.length - 1; i >= 0; i--) {
const subtopicElement = unitRefs.current[i];
if (subtopicElement) {
const subtopicTop = subtopicElement.offsetTop;
const subtopicHeight = subtopicElement.offsetHeight;
if (
scrollPosition >= subtopicTop &&
scrollPosition < subtopicTop + subtopicHeight
) {
dispatch(
setStickyHeaderId(subtopics[i].subtopic_number)
);
return;
}
}
}
}
// If we're before all subtopics, show no sticky header
dispatch(setStickyHeaderId(null));
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [subtopics, dispatch]);
// ============================================
// HANDLERS
// ============================================
const handleCourseSelect = (courseId: string) => {
setSelectedCourseId(courseId);
setSelectedTopicId(null);
};
const handleTopicSelect = (topicId: string) => {
setSelectedTopicId(topicId);
};
const handlePractice = () => {
console.log('Starting practice session');
};
const handleScrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const handleJumpToNextCourse = () => {
console.log('Jumping to next course');
};
// ============================================
// LOADING & ERROR STATES
// ============================================
if (subtopicsError) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="text-center max-w-md">
<p className="text-red-600 mb-4">Failed to load course content</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-primary-800 text-white rounded-lg hover:bg-primary-900"
>
Retry
</button>
</div>
</div>
);
}
// ============================================
// RENDER
// ============================================
return (
<div className="flex justify-center container">
{/* Main Learning Path Content - Full Width */}
<div className="flex flex-col w-full">
{/* Course & Topic Switcher */}
<div className="flex justify-end mb-4">
<CourseSwitcher
selectedCourseId={selectedCourseId}
selectedTopicId={selectedTopicId}
onCourseSelect={handleCourseSelect}
onTopicSelect={handleTopicSelect}
/>
</div>
{/* Subtopics Path */}
{subtopicsLoading ? (
<div className="flex justify-center py-16">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-800 mx-auto mb-4"></div>
<p className="text-virtu_grey-600">
Loading content...
</p>
</div>
</div>
) : (
<>
<UnitPath
subtopics={subtopics}
progress={progress}
stickyHeaderId={stickyHeaderId}
unitRefs={unitRefs}
fullWidth={true}
/>
{/* Course Completion Section */}
<CourseCompletionSection
nextCourseName="Linear Algebra"
nextCourseDescription="Learn how linear algebra helps in solving computer science problems."
onJumpAhead={handleJumpToNextCourse}
/>
</>
)}
{/* Floating Action Buttons */}
<div className="sticky bottom-28 left-0 right-0 flex items-end justify-between">
<PracticeButton onClick={handlePractice} position="left" />
<ScrollToTopButton
visible={scrollY > 100}
onClick={handleScrollToTop}
position="right"
/>
</div>
</div>
</div>
);
};
export default LearningPathPage;