Skip to content

Commit 370ed08

Browse files
committed
feat implement filtering by organization
1 parent ce275bc commit 370ed08

File tree

3 files changed

+89
-7
lines changed

3 files changed

+89
-7
lines changed

src/learningpath/Dashboard.jsx

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@openedx/paragon';
88
import { getConfig } from '@edx/frontend-platform';
99
import { FilterAlt, FilterList, Search } from '@openedx/paragon/icons';
10-
import { useLearningPaths, useLearnerDashboard } from './data/queries';
10+
import { useLearningPaths, useLearnerDashboard, useOrganizations } from './data/queries';
1111
import LearningPathCard from './LearningPathCard';
1212
import { CourseCard } from './CourseCard';
1313
import FilterPanel from './FilterPanel';
@@ -29,11 +29,16 @@ const Dashboard = () => {
2929
error: coursesError,
3030
} = useLearnerDashboard();
3131

32+
const {
33+
data: organizations,
34+
isLoading: isLoadingOrgs,
35+
} = useOrganizations();
36+
3237
const courses = learnerDashboardData?.courses;
3338
const emailConfirmation = learnerDashboardData?.emailConfirmation;
3439
const enterpriseDashboard = learnerDashboardData?.enterpriseDashboard;
3540

36-
const isLoading = isLoadingPaths || isLoadingCourses;
41+
const isLoading = isLoadingPaths || isLoadingCourses || isLoadingOrgs;
3742
const error = pathsError || coursesError;
3843

3944
if (error) {
@@ -77,6 +82,7 @@ const Dashboard = () => {
7782
const selectedContentTypeKey = 'lp_dashboard_contentType';
7883
const selectedStatusesKey = 'lp_dashboard_selectedStatuses';
7984
const selectedDateStatusesKey = 'lp_dashboard_selectedDateStatuses';
85+
const selectedOrgsKey = 'lp_dashboard_selectedOrgs';
8086

8187
const [showFilters, setShowFilters] = useState(() => localStorage.getItem(showFiltersKey) === 'true');
8288
const [selectedContentType, setSelectedContentType] = useState(() => localStorage.getItem(selectedContentTypeKey) || 'All');
@@ -86,6 +92,9 @@ const Dashboard = () => {
8692
const [selectedDateStatuses, setSelectedDateStatuses] = useState(
8793
() => JSON.parse(localStorage.getItem(selectedDateStatusesKey)) || [],
8894
);
95+
const [selectedOrgs, setSelectedOrgs] = useState(
96+
() => JSON.parse(localStorage.getItem(selectedOrgsKey)) || [],
97+
);
8998

9099
useEffect(() => { localStorage.setItem(showFiltersKey, showFilters.toString()); }, [showFilters]);
91100
useEffect(() => {
@@ -95,6 +104,8 @@ const Dashboard = () => {
95104
useEffect(() => {
96105
localStorage.setItem(selectedDateStatusesKey, JSON.stringify(selectedDateStatuses));
97106
}, [selectedDateStatuses]);
107+
useEffect(() => { localStorage.setItem(selectedOrgsKey, JSON.stringify(selectedOrgs)); }, [selectedOrgs]);
108+
useEffect(() => { localStorage.setItem(selectedOrgsKey, JSON.stringify(selectedOrgs)); }, [selectedOrgs]);
98109

99110
const handleStatusChange = (status, isChecked) => {
100111
setSelectedStatuses(prev => {
@@ -114,15 +125,46 @@ const Dashboard = () => {
114125
});
115126
};
116127

128+
const handleOrgChange = (org, isChecked) => {
129+
setSelectedOrgs(prev => {
130+
if (isChecked) {
131+
return [...prev, org];
132+
}
133+
return prev.filter(s => s !== org);
134+
});
135+
};
136+
117137
const handleClearFilters = () => {
118138
setSelectedContentType('All');
119139
setSelectedStatuses([]);
120140
setSelectedDateStatuses([]);
141+
setSelectedOrgs([]);
121142
};
122143

144+
// Get only the organizations that are present in the user's items.
145+
const availableOrganizations = useMemo(() => {
146+
if (!organizations || !items.length) { return {}; }
147+
148+
const availableOrgKeys = new Set();
149+
items.forEach(item => {
150+
if (item.org) {
151+
availableOrgKeys.add(item.org);
152+
}
153+
});
154+
155+
const filteredOrgs = {};
156+
availableOrgKeys.forEach(orgKey => {
157+
if (organizations[orgKey]) {
158+
filteredOrgs[orgKey] = organizations[orgKey];
159+
}
160+
});
161+
162+
return filteredOrgs;
163+
}, [organizations, items]);
164+
123165
const activeFiltersCount = useMemo(
124-
() => (selectedContentType !== 'All') + selectedStatuses.length + selectedDateStatuses.length,
125-
[selectedContentType, selectedStatuses, selectedDateStatuses],
166+
() => (selectedContentType !== 'All') + selectedStatuses.length + selectedDateStatuses.length + selectedOrgs.length,
167+
[selectedContentType, selectedStatuses, selectedDateStatuses, selectedOrgs],
126168
);
127169

128170
const getItemDates = (item) => {
@@ -160,11 +202,12 @@ const Dashboard = () => {
160202
|| (selectedContentType === 'learning_path' && item.type === 'learning_path');
161203
const statusMatch = selectedStatuses.length === 0 || selectedStatuses.includes(item.status);
162204
const dateStatusMatch = selectedDateStatuses.length === 0 || selectedDateStatuses.includes(getDateStatus(item));
205+
const orgMatch = selectedOrgs.length === 0 || selectedOrgs.includes(item.org);
163206
const searchMatch = searchQuery === ''
164207
|| (item.displayName && item.displayName.toLowerCase().includes(searchQuery.toLowerCase()))
165208
|| (item.name && item.name.toLowerCase().includes(searchQuery.toLowerCase()));
166-
return typeMatch && statusMatch && dateStatusMatch && searchMatch;
167-
}), [items, selectedContentType, selectedStatuses, selectedDateStatuses, searchQuery, getDateStatus]);
209+
return typeMatch && statusMatch && dateStatusMatch && orgMatch && searchMatch;
210+
}), [items, selectedContentType, selectedStatuses, selectedDateStatuses, selectedOrgs, searchQuery, getDateStatus]);
168211

169212
const sortedItems = useMemo(() => {
170213
const statusOrder = { 'not started': 1, 'in progress': 2, completed: 3 };
@@ -214,7 +257,7 @@ const Dashboard = () => {
214257
// Reset pagination when using filters or search.
215258
useEffect(() => {
216259
setCurrentPage(1);
217-
}, [searchQuery, selectedContentType, selectedStatuses, selectedDateStatuses]);
260+
}, [searchQuery, selectedContentType, selectedStatuses, selectedDateStatuses, selectedOrgs]);
218261

219262
return (
220263
<>
@@ -248,6 +291,9 @@ const Dashboard = () => {
248291
onChangeStatus={handleStatusChange}
249292
selectedDateStatuses={selectedDateStatuses}
250293
onChangeDateStatus={handleDateStatusChange}
294+
selectedOrgs={selectedOrgs}
295+
onChangeOrg={handleOrgChange}
296+
organizations={availableOrganizations}
251297
onClose={() => setShowFilters(false)}
252298
isSmall={isSmall}
253299
onClearAll={handleClearFilters}

src/learningpath/FilterPanel.jsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const FilterPanel = ({
1212
onChangeStatus,
1313
selectedDateStatuses,
1414
onChangeDateStatus,
15+
selectedOrgs,
16+
onChangeOrg,
17+
organizations,
1518
onClose,
1619
isSmall,
1720
onClearAll,
@@ -90,6 +93,26 @@ const FilterPanel = ({
9093
</Form.Group>
9194
</div>
9295

96+
{/* Organization Checkboxes */}
97+
{organizations && Object.keys(organizations).length > 0 && (
98+
<div className="my-3">
99+
<Form.Group>
100+
<Form.Label className="h4 my-3">Organization</Form.Label>
101+
<Form.CheckboxSet
102+
name="organization"
103+
onChange={e => onChangeOrg(e.target.value, e.target.checked)}
104+
value={selectedOrgs}
105+
>
106+
{Object.entries(organizations).map(([shortName, org]) => (
107+
<Form.Checkbox key={shortName} value={shortName} className="font-weight-light">
108+
{org.name || shortName}
109+
</Form.Checkbox>
110+
))}
111+
</Form.CheckboxSet>
112+
</Form.Group>
113+
</div>
114+
)}
115+
93116
{/* Action Buttons */}
94117
{isSmall && (
95118
<ButtonGroup className="pb-4 filter-actions">
@@ -107,6 +130,14 @@ FilterPanel.propTypes = {
107130
onChangeStatus: PropTypes.func.isRequired,
108131
selectedDateStatuses: PropTypes.arrayOf(PropTypes.string).isRequired,
109132
onChangeDateStatus: PropTypes.func.isRequired,
133+
selectedOrgs: PropTypes.arrayOf(PropTypes.string).isRequired,
134+
onChangeOrg: PropTypes.func.isRequired,
135+
organizations: PropTypes.objectOf(
136+
PropTypes.shape({
137+
name: PropTypes.string,
138+
shortName: PropTypes.string,
139+
}),
140+
).isRequired,
110141
onClose: PropTypes.func.isRequired,
111142
isSmall: PropTypes.bool.isRequired,
112143
onClearAll: PropTypes.func.isRequired,

src/learningpath/data/queries.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ export const useLearnerDashboard = () => {
212212
return {
213213
...courseWithLearningPaths,
214214
type: 'course',
215+
org: course.id ? course.id.match(/course-v1:([^+]+)/)?.[1] : null,
215216
enrollmentDate: course.enrollmentDate ? new Date(course.enrollmentDate) : null,
216217
};
217218
});
@@ -255,18 +256,21 @@ export const useCoursesByIds = (courseIds) => {
255256
...cachedCourseDetail,
256257
...addCompletionStatus(cachedCourseDetail, completionsMap, courseId),
257258
type: 'course',
259+
org: courseId ? courseId.match(/course-v1:([^+]+)/)?.[1] : null,
258260
};
259261
}
260262

261263
const detail = await api.fetchCourseDetails(courseId);
262264
queryClient.setQueryData(QUERY_KEYS.COURSE_DETAILS(courseId), {
263265
...detail,
264266
type: 'course',
267+
org: courseId ? courseId.match(/course-v1:([^+]+)/)?.[1] : null,
265268
});
266269

267270
return {
268271
...addCompletionStatus(detail, completionsMap, courseId),
269272
type: 'course',
273+
org: courseId ? courseId.match(/course-v1:([^+]+)/)?.[1] : null,
270274
};
271275
}),
272276
);
@@ -295,6 +299,7 @@ export const useCourseDetail = (courseKey) => {
295299
return {
296300
...addCompletionStatus(detail, completionsMap, courseKey),
297301
type: 'course',
302+
org: courseKey ? courseKey.match(/course-v1:([^+]+)/)?.[1] : null,
298303
};
299304
},
300305
enabled: !!courseKey,

0 commit comments

Comments
 (0)