Skip to content

Commit fa2c158

Browse files
Merge pull request #4364 from OneCommunityGlobal/vamsidhar-fix-lesson-list-tags
Vamsidhar - lesson list buttons sorting and filtering features
2 parents 1fde99f + 722ae10 commit fa2c158

3 files changed

Lines changed: 263 additions & 54 deletions

File tree

src/components/BMDashboard/LessonList/LessonCard.jsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Card from 'react-bootstrap/Card';
44
import Nav from 'react-bootstrap/Nav';
55
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
66
import { faHeart } from '@fortawesome/free-regular-svg-icons';
7-
import parse from 'html-react-parser';
7+
import ReactHtmlParser from 'html-react-parser';
88
import { formatDateAndTime } from '~/utils/formatDate';
99
import DeleteLessonCardPopUp from './DeleteLessonCardPopUp';
1010
import styles from './LessonCard.module.css';
@@ -77,7 +77,7 @@ function LessonCard({ filteredLessons, onEditLessonSummary, onDeliteLessonCard,
7777
handleLike(lessonId, userId);
7878
};
7979

80-
const lessonCards = filteredLessons.map(lesson => {
80+
const lessonCards = (filteredLessons || []).map(lesson => {
8181
const { isLiked, totalLikes } = getLikeStatus(lesson._id);
8282
return (
8383
<Card
@@ -102,9 +102,9 @@ function LessonCard({ filteredLessons, onEditLessonSummary, onDeliteLessonCard,
102102
<Nav.Item className={`${styles.lessonCardTag}`}>
103103
{lesson.tags &&
104104
lesson.tags.length > 0 &&
105-
lesson.tags.map(tag => (
105+
lesson.tags.map((tag, index) => (
106106
<span
107-
key={`tag-in-header-${tag}-${lesson._id}`}
107+
key={`tag-in-header-${tag}-${lesson._id}-${index}`}
108108
className={`text-muted ${styles.tagItem}`}
109109
>
110110
{`#${tag}`}
@@ -121,9 +121,9 @@ function LessonCard({ filteredLessons, onEditLessonSummary, onDeliteLessonCard,
121121
Tags:{' '}
122122
{lesson.tags &&
123123
lesson.tags.length > 0 &&
124-
lesson.tags.map(tag => (
124+
lesson.tags.map((tag, index) => (
125125
<span
126-
key={`tag-in-body-${tag}-${lesson._id}`}
126+
key={`tag-in-body-${tag}-${lesson._id}-${index}`}
127127
className={`text-muted ${styles.tagItem}`}
128128
>
129129
{`#${tag}`}
@@ -224,6 +224,14 @@ function LessonCard({ filteredLessons, onEditLessonSummary, onDeliteLessonCard,
224224
);
225225
});
226226

227+
if (!filteredLessons || filteredLessons.length === 0) {
228+
return (
229+
<div style={{ padding: '20px', textAlign: 'center' }}>
230+
<p>No lessons found. Please add lessons to the database or adjust your filters.</p>
231+
</div>
232+
);
233+
}
234+
227235
return (
228236
<div>
229237
<div style={{ textAlign: 'right' }}>

src/components/BMDashboard/LessonList/LessonListForm.jsx

Lines changed: 141 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function LessonList(props) {
1616
const [tags, setTags] = useState([]);
1717
const [inputValue, setInputValue] = useState('');
1818
const [deleteValue, setDeleteInputValue] = useState('');
19-
const [filteredLessons, setFilteredLessons] = useState(lessons);
19+
const [filteredLessons, setFilteredLessons] = useState(lessons || []);
2020
const [filterOption, setFilterOption] = useState('1');
2121
const [sortOption, setSortOption] = useState('1');
2222
const [availableTags, setAvailableTags] = useState([]);
@@ -27,6 +27,24 @@ function LessonList(props) {
2727
const [showExportModal, setShowExportModal] = useState(false);
2828
const [isExporting, setIsExporting] = useState(false);
2929

30+
// Load saved tags from localStorage on mount
31+
useEffect(() => {
32+
const savedTags = localStorage.getItem('lessonListSelectedTags');
33+
if (savedTags) {
34+
try {
35+
const parsedTags = JSON.parse(savedTags);
36+
if (Array.isArray(parsedTags) && parsedTags.length > 0) {
37+
// Remove duplicates when loading from localStorage
38+
const uniqueTags = [...new Set(parsedTags)];
39+
setTags(uniqueTags);
40+
}
41+
} catch (error) {
42+
// If parsing fails, ignore and use empty array
43+
console.error('Failed to parse saved tags:', error);
44+
}
45+
}
46+
}, []);
47+
3048
useEffect(() => {
3149
const fetchData = async () => {
3250
try {
@@ -40,6 +58,15 @@ function LessonList(props) {
4058
fetchData();
4159
}, [dispatch]);
4260

61+
// Save tags to localStorage whenever they change
62+
useEffect(() => {
63+
if (tags.length > 0) {
64+
localStorage.setItem('lessonListSelectedTags', JSON.stringify(tags));
65+
} else {
66+
localStorage.removeItem('lessonListSelectedTags');
67+
}
68+
}, [tags]);
69+
4370
useEffect(() => {
4471
if (lessons) {
4572
setFilteredLessons(lessons);
@@ -153,6 +180,14 @@ function LessonList(props) {
153180

154181
useEffect(() => {
155182
const handleClickOutside = event => {
183+
// Don't close dropdown if clicking on tag close button or tag container
184+
const buttonClose = event.target.closest('button[aria-label*="Remove"]');
185+
const tagContainer = event.target.closest(`.${styles.tagContainer}`);
186+
187+
if (buttonClose || tagContainer) {
188+
return;
189+
}
190+
156191
if (!event.target.closest('.tags-input-container')) {
157192
setShowDropdown(false);
158193
setShowDeleteDropdown(false);
@@ -165,9 +200,16 @@ function LessonList(props) {
165200
}, []);
166201

167202
const addTag = tag => {
168-
// Check if the tag already exists
169-
if (tags.indexOf(tag) === -1) {
170-
setTags(prevTags => [...prevTags, tag]);
203+
// Check if the tag already exists (case-insensitive check)
204+
const tagLower = tag.toLowerCase();
205+
const tagExists = tags.some(existingTag => existingTag.toLowerCase() === tagLower);
206+
207+
if (!tagExists) {
208+
setTags(prevTags => {
209+
// Ensure no duplicates when adding
210+
const newTags = [...prevTags, tag];
211+
return [...new Set(newTags)];
212+
});
171213
}
172214
setInputValue('');
173215
};
@@ -190,14 +232,22 @@ function LessonList(props) {
190232
setConfirmModal(true);
191233
};
192234

193-
const removeTag = index => {
194-
const newTags = [...tags];
195-
newTags.splice(index, 1);
196-
setTags(newTags);
235+
const removeTag = tagToRemove => {
236+
if (!tagToRemove) return;
237+
238+
setTags(prevTags => {
239+
// Filter out the tag to remove, using exact match
240+
const newTags = prevTags.filter(tag => tag !== tagToRemove);
241+
return newTags;
242+
});
197243
};
198244

199245
useEffect(() => {
200246
const applyFiltersAndSort = () => {
247+
if (!lessons || !Array.isArray(lessons) || lessons.length === 0) {
248+
setFilteredLessons([]);
249+
return;
250+
}
201251
let filtered = [...lessons];
202252

203253
// 1. Apply tag filtering
@@ -622,16 +672,52 @@ function LessonList(props) {
622672
</div>
623673
)}
624674
</InputGroup>
625-
<div className={`${styles.tagContainer}`}>
626-
{tags.map(tag => (
627-
<div key={tag} className={`${styles.tag}`}>
628-
<span>{tag}</span>
629-
<Button className={`${styles.buttonClose}`} onClick={() => removeTag(tag)}>
630-
x
631-
</Button>
632-
</div>
633-
))}
634-
</div>
675+
{tags.length > 0 && (
676+
<div
677+
className={`${styles.tagContainer} ${darkMode ? styles.tagContainerDark : ''}`}
678+
>
679+
{tags.map((tag, index) => {
680+
const handleRemoveClick = e => {
681+
e.preventDefault();
682+
e.stopPropagation();
683+
removeTag(tag);
684+
};
685+
686+
return (
687+
<div
688+
key={`filter-tag-${tag}-${index}`}
689+
className={`${styles.tag} ${darkMode ? styles.tagDark : ''}`}
690+
>
691+
<span className={darkMode ? styles.tagTextDark : ''}>{tag}</span>
692+
<span
693+
role="button"
694+
tabIndex={0}
695+
className={`${styles.buttonClose} ${
696+
darkMode ? styles.buttonCloseDark : ''
697+
}`}
698+
onClick={handleRemoveClick}
699+
onKeyDown={e => {
700+
if (e.key === 'Enter' || e.key === ' ') {
701+
handleRemoveClick(e);
702+
}
703+
}}
704+
aria-label={`Remove ${tag} tag`}
705+
style={{
706+
pointerEvents: 'auto',
707+
zIndex: 100,
708+
cursor: 'pointer',
709+
display: 'inline-flex',
710+
alignItems: 'center',
711+
justifyContent: 'center',
712+
}}
713+
>
714+
×
715+
</span>
716+
</div>
717+
);
718+
})}
719+
</div>
720+
)}
635721
</div>
636722

637723
<Form.Label>Delete Tags (Press enter to add a tag to delete): </Form.Label>
@@ -664,20 +750,42 @@ function LessonList(props) {
664750
</div>
665751
)}
666752
<div className={`${styles.tagContainer}`}>
667-
{tagsToDelete.map(tag => (
668-
<div key={tag} className={`${styles.tag}`}>
669-
<span>{tag}</span>
670-
<Button
671-
className={`${styles.buttonClose}`}
672-
onClick={() => {
673-
const newTags = tagsToDelete.filter((_, i) => i !== tag);
674-
setTagsToDelete(newTags);
675-
}}
676-
>
677-
x
678-
</Button>
679-
</div>
680-
))}
753+
{tagsToDelete.map((tag, index) => {
754+
const handleRemoveDeleteTag = e => {
755+
e.preventDefault();
756+
e.stopPropagation();
757+
const newTags = tagsToDelete.filter(t => t !== tag);
758+
setTagsToDelete(newTags);
759+
};
760+
761+
return (
762+
<div key={`delete-tag-${tag}-${index}`} className={`${styles.tag}`}>
763+
<span>{tag}</span>
764+
<span
765+
role="button"
766+
tabIndex={0}
767+
className={`${styles.buttonClose}`}
768+
onClick={handleRemoveDeleteTag}
769+
onKeyDown={e => {
770+
if (e.key === 'Enter' || e.key === ' ') {
771+
handleRemoveDeleteTag(e);
772+
}
773+
}}
774+
aria-label={`Remove ${tag} from delete list`}
775+
style={{
776+
pointerEvents: 'auto',
777+
zIndex: 100,
778+
cursor: 'pointer',
779+
display: 'inline-flex',
780+
alignItems: 'center',
781+
justifyContent: 'center',
782+
}}
783+
>
784+
×
785+
</span>
786+
</div>
787+
);
788+
})}
681789
</div>
682790
</div>
683791
{tagsToDelete.length > 0 && (
@@ -720,7 +828,7 @@ function LessonList(props) {
720828
const mapStateToProps = state => {
721829
return {
722830
lessons: state.lessons.lessons,
723-
darkMode: state.theme.darkMode,
831+
darkMode: state.theme?.darkMode || false,
724832
};
725833
};
726834

0 commit comments

Comments
 (0)