Skip to content

Commit ce275bc

Browse files
committed
feat: implement date-based filtering
1 parent 767e134 commit ce275bc

File tree

3 files changed

+97
-80
lines changed

3 files changed

+97
-80
lines changed

src/learningpath/Dashboard.jsx

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {
2-
useState, useMemo, useEffect, useRef,
2+
useState, useMemo, useEffect, useRef, useCallback,
33
} from 'react';
44
import { Link } from 'react-router-dom';
55
import {
@@ -76,18 +76,25 @@ const Dashboard = () => {
7676
const showFiltersKey = 'lp_dashboard_showFilters';
7777
const selectedContentTypeKey = 'lp_dashboard_contentType';
7878
const selectedStatusesKey = 'lp_dashboard_selectedStatuses';
79+
const selectedDateStatusesKey = 'lp_dashboard_selectedDateStatuses';
7980

8081
const [showFilters, setShowFilters] = useState(() => localStorage.getItem(showFiltersKey) === 'true');
8182
const [selectedContentType, setSelectedContentType] = useState(() => localStorage.getItem(selectedContentTypeKey) || 'All');
8283
const [selectedStatuses, setSelectedStatuses] = useState(
8384
() => JSON.parse(localStorage.getItem(selectedStatusesKey)) || [],
8485
);
86+
const [selectedDateStatuses, setSelectedDateStatuses] = useState(
87+
() => JSON.parse(localStorage.getItem(selectedDateStatusesKey)) || [],
88+
);
8589

8690
useEffect(() => { localStorage.setItem(showFiltersKey, showFilters.toString()); }, [showFilters]);
8791
useEffect(() => {
8892
localStorage.setItem(selectedContentTypeKey, selectedContentType.toString());
8993
}, [selectedContentType]);
9094
useEffect(() => { localStorage.setItem(selectedStatusesKey, JSON.stringify(selectedStatuses)); }, [selectedStatuses]);
95+
useEffect(() => {
96+
localStorage.setItem(selectedDateStatusesKey, JSON.stringify(selectedDateStatuses));
97+
}, [selectedDateStatuses]);
9198

9299
const handleStatusChange = (status, isChecked) => {
93100
setSelectedStatuses(prev => {
@@ -98,60 +105,78 @@ const Dashboard = () => {
98105
});
99106
};
100107

108+
const handleDateStatusChange = (dateStatus, isChecked) => {
109+
setSelectedDateStatuses(prev => {
110+
if (isChecked) {
111+
return [...prev, dateStatus];
112+
}
113+
return prev.filter(s => s !== dateStatus);
114+
});
115+
};
116+
101117
const handleClearFilters = () => {
102118
setSelectedContentType('All');
103119
setSelectedStatuses([]);
120+
setSelectedDateStatuses([]);
104121
};
105122

106123
const activeFiltersCount = useMemo(
107-
() => (selectedContentType !== 'All') + selectedStatuses.length,
108-
[selectedContentType, selectedStatuses],
124+
() => (selectedContentType !== 'All') + selectedStatuses.length + selectedDateStatuses.length,
125+
[selectedContentType, selectedStatuses, selectedDateStatuses],
109126
);
110127

128+
const getItemDates = (item) => {
129+
if (item.type === 'course') {
130+
return {
131+
startDate: item.startDate ? new Date(item.startDate) : null,
132+
endDate: item.endDate ? new Date(item.endDate) : null,
133+
};
134+
}
135+
if (item.type === 'learning_path') {
136+
return {
137+
startDate: item.minDate ? new Date(item.minDate) : null,
138+
endDate: item.maxDate ? new Date(item.maxDate) : null,
139+
};
140+
}
141+
return { startDate: null, endDate: null };
142+
};
143+
144+
const getDateStatus = useCallback((item) => {
145+
const currentDate = new Date();
146+
const { startDate, endDate } = getItemDates(item);
147+
148+
if (startDate && startDate > currentDate) {
149+
return 'Upcoming';
150+
}
151+
if (endDate && endDate < currentDate) {
152+
return 'Ended';
153+
}
154+
return 'Open';
155+
}, []);
156+
111157
const filteredItems = useMemo(() => items.filter(item => {
112158
const typeMatch = selectedContentType === 'All'
113-
|| (selectedContentType === 'course' && item.type === 'course')
114-
|| (selectedContentType === 'learning_path' && item.type === 'learning_path');
159+
|| (selectedContentType === 'course' && item.type === 'course')
160+
|| (selectedContentType === 'learning_path' && item.type === 'learning_path');
115161
const statusMatch = selectedStatuses.length === 0 || selectedStatuses.includes(item.status);
162+
const dateStatusMatch = selectedDateStatuses.length === 0 || selectedDateStatuses.includes(getDateStatus(item));
116163
const searchMatch = searchQuery === ''
117164
|| (item.displayName && item.displayName.toLowerCase().includes(searchQuery.toLowerCase()))
118165
|| (item.name && item.name.toLowerCase().includes(searchQuery.toLowerCase()));
119-
return typeMatch && statusMatch && searchMatch;
120-
}), [items, selectedContentType, selectedStatuses, searchQuery]);
166+
return typeMatch && statusMatch && dateStatusMatch && searchMatch;
167+
}), [items, selectedContentType, selectedStatuses, selectedDateStatuses, searchQuery, getDateStatus]);
121168

122169
const sortedItems = useMemo(() => {
123-
const currentDate = new Date();
124-
125-
const getStartDateCategory = (item) => {
126-
let startDate = null;
127-
let endDate = null;
128-
129-
if (item.type === 'course') {
130-
startDate = item.startDate ? new Date(item.startDate) : null;
131-
endDate = item.endDate ? new Date(item.endDate) : null;
132-
} else if (item.type === 'learning_path') {
133-
startDate = item.minDate ? new Date(item.minDate) : null;
134-
endDate = item.maxDate ? new Date(item.maxDate) : null;
135-
}
136-
137-
if (startDate && startDate > currentDate) {
138-
return 1; // Not started.
139-
}
140-
if (endDate && endDate < currentDate) {
141-
return 3; // Ended.
142-
}
143-
return 2; // Available.
144-
};
145-
146170
const statusOrder = { 'not started': 1, 'in progress': 2, completed: 3 };
171+
const dateStatusOrder = { Upcoming: 1, Open: 2, Ended: 3 };
147172

148173
return [...filteredItems].sort((a, b) => {
149174
// 1. Sort by start date category.
150-
const startCategoryA = getStartDateCategory(a);
151-
const startCategoryB = getStartDateCategory(b);
175+
const dateStatusA = dateStatusOrder[getDateStatus(a)] || 999;
176+
const dateStatusB = dateStatusOrder[getDateStatus(b)] || 999;
152177

153-
if (startCategoryA !== startCategoryB) {
154-
return startCategoryA - startCategoryB;
178+
if (dateStatusA !== dateStatusB) {
179+
return dateStatusA - dateStatusB;
155180
}
156181

157182
// 2. Sort by progress status.
@@ -168,7 +193,7 @@ const Dashboard = () => {
168193

169194
return nameA.localeCompare(nameB);
170195
});
171-
}, [filteredItems]);
196+
}, [filteredItems, getDateStatus]);
172197

173198
const PAGE_SIZE = getConfig().DASHBOARD_PAGE_SIZE || 10;
174199
const [currentPage, setCurrentPage] = useState(1);
@@ -189,7 +214,7 @@ const Dashboard = () => {
189214
// Reset pagination when using filters or search.
190215
useEffect(() => {
191216
setCurrentPage(1);
192-
}, [searchQuery, selectedContentType, selectedStatuses]);
217+
}, [searchQuery, selectedContentType, selectedStatuses, selectedDateStatuses]);
193218

194219
return (
195220
<>
@@ -221,6 +246,8 @@ const Dashboard = () => {
221246
onSelectContentType={setSelectedContentType}
222247
selectedStatuses={selectedStatuses}
223248
onChangeStatus={handleStatusChange}
249+
selectedDateStatuses={selectedDateStatuses}
250+
onChangeDateStatus={handleDateStatusChange}
224251
onClose={() => setShowFilters(false)}
225252
isSmall={isSmall}
226253
onClearAll={handleClearFilters}

src/learningpath/FilterPanel.jsx

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const FilterPanel = ({
1010
onSelectContentType,
1111
selectedStatuses,
1212
onChangeStatus,
13+
selectedDateStatuses,
14+
onChangeDateStatus,
1315
onClose,
1416
isSmall,
1517
onClearAll,
@@ -58,46 +60,41 @@ const FilterPanel = ({
5860

5961
{/* Status Checkboxes */}
6062
<div className="my-3">
61-
<h4 className="mt-4.5 mb-3">My Progress</h4>
62-
<Form>
63-
<div className="status-options">
64-
<Form.Checkbox
65-
value="In progress"
66-
checked={selectedStatuses.includes('In progress')}
67-
onChange={e => onChangeStatus('In progress', e.target.checked)}
68-
className="font-weight-light"
69-
>
70-
In progress
71-
</Form.Checkbox>
72-
<Form.Checkbox
73-
value="Not started"
74-
checked={selectedStatuses.includes('Not started')}
75-
onChange={e => onChangeStatus('Not started', e.target.checked)}
76-
className="font-weight-light"
77-
>
78-
Not started
79-
</Form.Checkbox>
80-
<Form.Checkbox
81-
value="Completed"
82-
checked={selectedStatuses.includes('Completed')}
83-
onChange={e => onChangeStatus('Completed', e.target.checked)}
84-
className="font-weight-light"
85-
>
86-
Completed
87-
</Form.Checkbox>
88-
</div>
89-
</Form>
63+
<Form.Group>
64+
<Form.Label className="h4 my-3">My Progress</Form.Label>
65+
<Form.CheckboxSet
66+
name="progress-status"
67+
onChange={e => onChangeStatus(e.target.value, e.target.checked)}
68+
value={selectedStatuses}
69+
>
70+
<Form.Checkbox value="In progress" className="font-weight-light">In progress</Form.Checkbox>
71+
<Form.Checkbox value="Not started" className="font-weight-light">Not started</Form.Checkbox>
72+
<Form.Checkbox value="Completed" className="font-weight-light">Completed</Form.Checkbox>
73+
</Form.CheckboxSet>
74+
</Form.Group>
75+
</div>
76+
77+
{/* Date Status Checkboxes */}
78+
<div className="my-3">
79+
<Form.Group>
80+
<Form.Label className="h4 my-3">Course / Learning Path Status</Form.Label>
81+
<Form.CheckboxSet
82+
name="date-status"
83+
onChange={e => onChangeDateStatus(e.target.value, e.target.checked)}
84+
value={selectedDateStatuses}
85+
>
86+
<Form.Checkbox value="Open" className="font-weight-light">Open</Form.Checkbox>
87+
<Form.Checkbox value="Upcoming" className="font-weight-light">Upcoming</Form.Checkbox>
88+
<Form.Checkbox value="Ended" className="font-weight-light">Ended</Form.Checkbox>
89+
</Form.CheckboxSet>
90+
</Form.Group>
9091
</div>
9192

9293
{/* Action Buttons */}
9394
{isSmall && (
9495
<ButtonGroup className="pb-4 filter-actions">
95-
<Button variant="outline-secondary" onClick={onClearAll}>
96-
Clear all
97-
</Button>
98-
<Button variant="primary" onClick={onClose} className="pl-3">
99-
Apply
100-
</Button>
96+
<Button variant="outline-secondary" onClick={onClearAll}>Clear all</Button>
97+
<Button variant="primary" onClick={onClose} className="pl-3">Apply</Button>
10198
</ButtonGroup>
10299
)}
103100
</div>
@@ -108,6 +105,8 @@ FilterPanel.propTypes = {
108105
onSelectContentType: PropTypes.func.isRequired,
109106
selectedStatuses: PropTypes.arrayOf(PropTypes.string).isRequired,
110107
onChangeStatus: PropTypes.func.isRequired,
108+
selectedDateStatuses: PropTypes.arrayOf(PropTypes.string).isRequired,
109+
onChangeDateStatus: PropTypes.func.isRequired,
111110
onClose: PropTypes.func.isRequired,
112111
isSmall: PropTypes.bool.isRequired,
113112
onClearAll: PropTypes.func.isRequired,

src/learningpath/index.css

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -624,15 +624,6 @@
624624
}
625625
}
626626

627-
.status-options {
628-
display: flex;
629-
flex-direction: column;
630-
631-
.pgn__form-checkbox {
632-
margin-bottom: 0.75rem;
633-
}
634-
}
635-
636627
.filter-actions {
637628
gap: 2rem;
638629

0 commit comments

Comments
 (0)