Skip to content

Commit 1151935

Browse files
fix: resolved the merge conflicts and testing it out on my local
2 parents 22b38f6 + b70f3ec commit 1151935

59 files changed

Lines changed: 7922 additions & 3241 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/actions/studentTasks.js

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
import axios from 'axios';
2+
import { toast } from 'react-toastify';
3+
import * as types from '../constants/studentTasks';
4+
import { ENDPOINTS } from '~/utils/URL';
5+
import { mockTasks } from '../components/EductionPortal/StudentDashboard/mockData';
6+
import httpService from '../services/httpService';
7+
8+
/**
9+
* Set a flag that fetching Student Tasks
10+
*/
11+
export const setStudentTasksStart = () => {
12+
return {
13+
type: types.FETCH_STUDENT_TASKS_START,
14+
};
15+
};
16+
17+
/**
18+
* Set Student Tasks in store
19+
* @param payload : Student Task []
20+
*/
21+
export const setStudentTasks = (taskItems) => {
22+
return {
23+
type: types.RECEIVE_STUDENT_TASKS,
24+
taskItems,
25+
};
26+
};
27+
28+
/**
29+
* Error when fetching student tasks
30+
* @param payload : error status code
31+
*/
32+
export const setStudentTasksError = (err) => {
33+
return {
34+
type: types.FETCH_STUDENT_TASKS_ERROR,
35+
err,
36+
};
37+
};
38+
39+
/**
40+
* Update a specific student task
41+
*/
42+
export const updateStudentTask = (taskId, updatedTask) => {
43+
return {
44+
type: types.UPDATE_STUDENT_TASK,
45+
taskId,
46+
updatedTask,
47+
};
48+
};
49+
50+
/**
51+
* Transform a single task to flat format
52+
* @param {Object} task - The task object
53+
* @param {Object} subjectData - The subject data containing subject info
54+
* @param {string} subjectKey - The subject key
55+
* @returns {Object} Transformed task in flat format
56+
*/
57+
const transformTaskToFlatFormat = (task, subjectData, subjectKey) => {
58+
return {
59+
id: task._id, // Use _id as the primary id for React keys
60+
course_name: subjectData.subject?.name || subjectKey || 'Unknown Subject',
61+
subtitle: task.lessonPlan?.title || task.atom?.name || 'No Description',
62+
task_type: task.type || 'read',
63+
logged_hours: task.loggedHours || 0,
64+
suggested_total_hours: task.suggestedTotalHours || 0,
65+
last_logged_date: task.completedAt || task.assignedAt,
66+
created_at: task.assignedAt,
67+
is_completed: task.status === 'completed' || task.status === 'graded',
68+
has_upload: task.uploadUrls && task.uploadUrls.length > 0,
69+
has_comments: task.feedback && task.feedback.length > 0,
70+
status: task.status || 'assigned',
71+
_id: task._id,
72+
grade: task.grade,
73+
feedback: task.feedback,
74+
dueAt: task.dueAt,
75+
lessonPlan: task.lessonPlan,
76+
subject: task.subject,
77+
atom: task.atom,
78+
color_level: task.color_level,
79+
difficulty_level: task.difficulty_level,
80+
activity_group: task.activity_group,
81+
};
82+
};
83+
84+
/**
85+
* Flatten grouped tasks structure to individual tasks array
86+
* @param {Object} groupedTasks - The grouped tasks from API response
87+
* @returns {Array} Array of flattened, deduplicated tasks
88+
*/
89+
const flattenGroupedTasks = (groupedTasks) => {
90+
const taskMap = new Map(); // Use Map to deduplicate tasks by _id
91+
92+
// Flatten the grouped structure to get individual tasks
93+
Object.entries(groupedTasks).forEach(([subjectKey, subjectData]) => {
94+
Object.values(subjectData.colorLevels).forEach(colorLevel => {
95+
Object.values(colorLevel.activityGroups).forEach(activityGroup => {
96+
activityGroup.tasks.forEach(task => {
97+
// Only add task if it hasn't been seen before (deduplication)
98+
if (!taskMap.has(task._id)) {
99+
const transformedTask = transformTaskToFlatFormat(task, subjectData, subjectKey);
100+
taskMap.set(task._id, transformedTask);
101+
}
102+
});
103+
});
104+
});
105+
});
106+
107+
// Convert Map values to array and add final deduplication as safety measure
108+
const flattenedTasks = Array.from(taskMap.values());
109+
110+
// Final deduplication by _id as a safety measure
111+
const uniqueTasks = flattenedTasks.filter((task, index, self) =>
112+
index === self.findIndex(t => t._id === task._id)
113+
);
114+
115+
return uniqueTasks;
116+
};
117+
118+
/**
119+
* Fetch tasks from the primary API endpoint
120+
* @returns {Promise<Array>} Array of flattened tasks
121+
*/
122+
const fetchTasksFromPrimaryEndpoint = async () => {
123+
console.log('Making API call to:', ENDPOINTS.STUDENT_TASKS());
124+
125+
const response = await httpService.get(ENDPOINTS.STUDENT_TASKS());
126+
console.log('API response:', response.data);
127+
128+
// The API returns grouped tasks, we need to flatten them for our UI
129+
const groupedTasks = response.data.tasks;
130+
const uniqueTasks = flattenGroupedTasks(groupedTasks);
131+
132+
console.log(`Processed ${uniqueTasks.length} unique tasks from API response`);
133+
return uniqueTasks;
134+
};
135+
136+
/**
137+
* Handle API error and try fallback options
138+
* @param {Error} apiError - The API error
139+
* @param {Function} dispatch - Redux dispatch function
140+
* @returns {Promise<Array>} Array of tasks (from fallback or mock data)
141+
*/
142+
const handleApiError = async (apiError, dispatch) => {
143+
console.error('Student tasks API error:', apiError);
144+
console.error('Error response:', apiError.response?.data);
145+
console.error('Error status:', apiError.response?.status);
146+
console.error('Error config:', apiError.config);
147+
148+
// Try alternative endpoint if the first one fails
149+
if (apiError.response?.status === 404) {
150+
console.log('Trying alternative endpoint...');
151+
try {
152+
const altResponse = await httpService.post(`${ENDPOINTS.APIEndpoint()}/student-tasks`);
153+
console.log('Alternative endpoint response:', altResponse.data);
154+
return altResponse.data.tasks || [];
155+
} catch (altError) {
156+
console.error('Alternative endpoint also failed:', altError);
157+
}
158+
}
159+
160+
console.warn('Student tasks API not available, using mock data:', apiError.message);
161+
toast.info('Using demo data. Student tasks API is not yet available.');
162+
return mockTasks;
163+
};
164+
165+
/**
166+
* Fetch all student tasks for the logged-in user
167+
*/
168+
export const fetchStudentTasks = () => {
169+
return async (dispatch, getState) => {
170+
dispatch(setStudentTasksStart());
171+
172+
try {
173+
const state = getState();
174+
const userId = state.auth.user.userid;
175+
176+
if (!userId) {
177+
console.error('No user ID found in auth state');
178+
dispatch(setStudentTasksError('User not authenticated'));
179+
return;
180+
}
181+
182+
try {
183+
const tasks = await fetchTasksFromPrimaryEndpoint();
184+
dispatch(setStudentTasks(tasks));
185+
} catch (apiError) {
186+
const fallbackTasks = await handleApiError(apiError, dispatch);
187+
dispatch(setStudentTasks(fallbackTasks));
188+
}
189+
} catch (err) {
190+
console.error('Error fetching student tasks:', err);
191+
dispatch(setStudentTasksError(err.message || 'Failed to fetch student tasks'));
192+
toast.error('Failed to fetch student tasks. Please try again later.');
193+
}
194+
};
195+
};
196+
197+
/**
198+
* Validate if a task can be marked as completed
199+
* @param {Object} task - The task to validate
200+
* @returns {Object} Validation result with valid flag and optional error message
201+
*/
202+
const validateTaskCompletion = (task) => {
203+
if (task.is_completed) {
204+
return { valid: false, errorMessage: 'Task is already completed' };
205+
}
206+
207+
if (task.task_type !== 'read') {
208+
return { valid: false, errorMessage: 'Only read tasks can be marked as complete manually' };
209+
}
210+
211+
if (task.logged_hours < task.suggested_total_hours) {
212+
return {
213+
valid: false,
214+
errorMessage: `Insufficient hours logged. Required: ${task.suggested_total_hours}, Logged: ${task.logged_hours}`
215+
};
216+
}
217+
218+
return { valid: true };
219+
};
220+
221+
/**
222+
* Call the mark-complete API endpoint
223+
* @param {string} taskId - The task ID
224+
* @param {string} userId - The user ID
225+
* @returns {Promise<void>}
226+
*/
227+
const callMarkCompleteAPI = async (taskId, userId) => {
228+
await httpService.post(`${ENDPOINTS.APIEndpoint()}/education-tasks/student/mark-complete`, {
229+
taskId: taskId,
230+
studentId: userId,
231+
requestor: {
232+
requestorId: userId
233+
}
234+
});
235+
};
236+
237+
/**
238+
* Mark a student task as done
239+
*/
240+
export const markStudentTaskAsDone = (taskId) => {
241+
return async (dispatch, getState) => {
242+
try {
243+
const state = getState();
244+
const task = state.studentTasks.taskItems.find(t => t.id === taskId);
245+
246+
if (!task) {
247+
throw new Error('Task not found');
248+
}
249+
250+
// Validate task can be marked as done
251+
const validation = validateTaskCompletion(task);
252+
if (!validation.valid) {
253+
if (task.is_completed) {
254+
toast.warning(validation.errorMessage);
255+
} else {
256+
toast.error(validation.errorMessage);
257+
}
258+
return;
259+
}
260+
261+
try {
262+
// Call the student mark-complete API endpoint
263+
await callMarkCompleteAPI(taskId, state.auth.user.userid);
264+
265+
// Only update local state if API call succeeds
266+
dispatch(updateStudentTask(taskId, {
267+
...task,
268+
is_completed: true,
269+
status: 'completed'
270+
}));
271+
272+
toast.success('Task marked as completed successfully!');
273+
} catch (apiError) {
274+
// Show error toast if API fails
275+
console.error('Student task mark complete API error:', apiError);
276+
console.error('API Error:', apiError.response?.data || apiError.message);
277+
278+
const errorMessage = apiError.response?.data?.error || apiError.message || 'Failed to mark task as complete';
279+
toast.error(`Error: ${errorMessage}`);
280+
}
281+
} catch (err) {
282+
console.error('Error marking task as done:', err);
283+
toast.error('Failed to mark task as done. Please try again.');
284+
}
285+
};
286+
};

src/actions/timeEntries.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export const postTimeEntry = timeEntry => {
202202
return async dispatch => {
203203
try {
204204
const res = await axios.post(url, timeEntry);
205-
if (timeEntry.entryType === 'default') {
205+
if (timeEntry.entryType === 'default' || timeEntry.entryType === 'person') {
206206
dispatch(updateTimeEntries(timeEntry));
207207
}
208208
return res.status;
@@ -217,7 +217,7 @@ export const editTimeEntry = (timeEntryId, timeEntry, oldDateOfWork) => {
217217
return async dispatch => {
218218
try {
219219
const res = await axios.put(url, timeEntry);
220-
if (timeEntry.entryType === 'default') {
220+
if (timeEntry.entryType === 'default' || timeEntry.entryType === 'person') {
221221
dispatch(updateTimeEntries(timeEntry, oldDateOfWork));
222222
}
223223
return res.status;
@@ -232,7 +232,7 @@ export const deleteTimeEntry = timeEntry => {
232232
return async dispatch => {
233233
try {
234234
const res = await axios.delete(url);
235-
if (timeEntry.entryType === 'default') {
235+
if (timeEntry.entryType === 'default' || timeEntry.entryType === 'person') {
236236
dispatch(updateTimeEntries(timeEntry));
237237
}
238238
return res.status;

src/actions/timelogTracking.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import axios from 'axios';
2+
import {
3+
GET_TIMELOG_TRACKING_START,
4+
GET_TIMELOG_TRACKING_SUCCESS,
5+
GET_TIMELOG_TRACKING_ERROR,
6+
} from '../constants/timelogTracking';
7+
import { ENDPOINTS } from '~/utils/URL';
8+
9+
export const getTimelogTrackingStart = () => ({
10+
type: GET_TIMELOG_TRACKING_START,
11+
});
12+
13+
export const getTimelogTrackingSuccess = data => ({
14+
type: GET_TIMELOG_TRACKING_SUCCESS,
15+
payload: data,
16+
});
17+
18+
export const getTimelogTrackingError = error => ({
19+
type: GET_TIMELOG_TRACKING_ERROR,
20+
payload: error,
21+
});
22+
23+
export const addTimelogEvent = event => ({
24+
type: 'ADD_TIMELOG_EVENT',
25+
payload: event,
26+
});
27+
28+
export const getTimelogTracking = userId => {
29+
const url = ENDPOINTS.TIMELOG_TRACKING(userId);
30+
return async dispatch => {
31+
dispatch(getTimelogTrackingStart());
32+
try {
33+
const response = await axios.get(url);
34+
dispatch(getTimelogTrackingSuccess(response.data));
35+
} catch (error) {
36+
dispatch(getTimelogTrackingError(error));
37+
}
38+
};
39+
};
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import React from 'react';
2+
import StudentDashboard from './StudentDashboard/StudentDashboard';
13
import './EPDashboard.module.css';
24

35
export function EPDashboard() {
4-
return <h1>Welcome to Education Portal</h1>;
6+
return <StudentDashboard />;
57
}
68

79
export default EPDashboard;

0 commit comments

Comments
 (0)