Skip to content

Commit 8ce7acf

Browse files
committed
Add stage filters to main concepts listing
1 parent fde84df commit 8ce7acf

File tree

2 files changed

+36
-7
lines changed

2 files changed

+36
-7
lines changed

src/app/components/elements/layout/SidebarLayout.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,23 @@ export const SubjectSpecificConceptListSidebar = (props: ConceptListSidebarProps
429429
</ContentSidebar>;
430430
};
431431

432-
export const GenericConceptsSidebar = (props: ConceptListSidebarProps) => {
433-
const { searchText, setSearchText, conceptFilters, setConceptFilters, applicableTags, tagCounts, ...rest } = props;
432+
interface GenericConceptsSidebarProps extends ConceptListSidebarProps {
433+
searchStages: STAGE[];
434+
setSearchStages: React.Dispatch<React.SetStateAction<STAGE[]>>;
435+
stageCounts: Record<string, number>;
436+
}
434437

435-
const pageContext = useAppSelector(selectors.pageContext.context);
438+
export const GenericConceptsSidebar = (props: GenericConceptsSidebarProps) => {
439+
const { searchText, setSearchText, conceptFilters, setConceptFilters, applicableTags, tagCounts, searchStages, setSearchStages, stageCounts, ...rest } = props;
436440

441+
const updateSearchStages = (stage: STAGE) => {
442+
if (searchStages.includes(stage)) {
443+
setSearchStages(searchStages.filter(s => s !== stage));
444+
} else {
445+
setSearchStages([...(searchStages ?? []), stage]);
446+
}
447+
};
448+
437449
return <ContentSidebar {...rest}>
438450
<div className="section-divider"/>
439451
<search>
@@ -473,6 +485,17 @@ export const GenericConceptsSidebar = (props: ConceptListSidebarProps) => {
473485
</div>}
474486
</div>;
475487
})}
488+
<div className="section-divider"/>
489+
<h5>Filter by stage</h5>
490+
<ul className="ps-2">
491+
{getFilteredStageOptions().map((stage, i) =>
492+
<li key={i}>
493+
<StyledCheckbox checked={searchStages.includes(stage.value)}
494+
label={<>{stage.label} <span className="text-muted">({stageCounts[stage.value]})</span></>}
495+
data-bs-theme={conceptFilters.length === 1 ? conceptFilters[0].id : undefined}
496+
color="theme" onChange={() => {updateSearchStages(stage.value);}}/>
497+
</li>)}
498+
</ul>
476499
</div>
477500
</search>
478501

src/app/components/pages/Concepts.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {Link, RouteComponentProps, withRouter} from "react-router-dom";
33
import {selectors, useAppSelector} from "../../state";
44
import {Badge, Card, CardBody, CardHeader, Container} from "reactstrap";
55
import queryString from "query-string";
6-
import {isAda, isPhy, isRelevantToPageContext, matchesAllWordsInAnyOrder, pushConceptsToHistory, searchResultIsPublic, shortcuts, TAG_ID, tags} from "../../services";
6+
import {getFilteredStageOptions, isAda, isPhy, isRelevantToPageContext, matchesAllWordsInAnyOrder, pushConceptsToHistory, searchResultIsPublic, shortcuts, STAGE, STAGE_TO_LEARNING_STAGE, TAG_ID, tags} from "../../services";
77
import {generateSubjectLandingPageCrumbFromContext, TitleAndBreadcrumb} from "../elements/TitleAndBreadcrumb";
88
import {ShortcutResponse, Tag} from "../../../IsaacAppTypes";
99
import {IsaacSpinner} from "../handlers/IsaacSpinner";
@@ -49,6 +49,7 @@ export const Concepts = withRouter((props: RouteComponentProps) => {
4949
const [conceptFilters, setConceptFilters] = useState<Tag[]>(
5050
applicableTags.filter(f => filters.includes(f.id))
5151
);
52+
const [searchStages, setSearchStages] = useState<STAGE[]>([]);
5253
const [shortcutResponse, setShortcutResponse] = useState<ShortcutResponse[]>();
5354

5455
const listConceptsQuery = useListConceptsQuery(pageContext
@@ -58,8 +59,8 @@ export const Concepts = withRouter((props: RouteComponentProps) => {
5859

5960
const shortcutAndFilter = (concepts?: ContentSummaryDTO[], excludeTopicFiltering?: boolean) => {
6061
const searchResults = concepts?.filter(c =>
61-
matchesAllWordsInAnyOrder(c.title, searchText || "") ||
62-
matchesAllWordsInAnyOrder(c.summary, searchText || "")
62+
(matchesAllWordsInAnyOrder(c.title, searchText || "") || matchesAllWordsInAnyOrder(c.summary, searchText || ""))
63+
&& (searchStages.length === 0 || searchStages.some(s => c.audience?.some(a => a.stage?.includes(s))))
6364
);
6465

6566
const filteredSearchResults = searchResults
@@ -81,6 +82,11 @@ export const Concepts = withRouter((props: RouteComponentProps) => {
8182
[t.id]: shortcutAndFilter(listConceptsQuery?.data?.results, true)?.filter(c => c.tags?.includes(t.id)).length || 0
8283
}), {});
8384

85+
const stageCounts = getFilteredStageOptions().reduce((acc, s) => ({
86+
...acc,
87+
[s.value]: shortcutAndFilter(listConceptsQuery?.data?.results, true)?.filter(c => c.audience?.some(a => a.stage?.includes(s.value))).length || 0
88+
}), {});
89+
8490
function doSearch(e?: FormEvent<HTMLFormElement>) {
8591
if (e) {
8692
e.preventDefault();
@@ -119,7 +125,7 @@ export const Concepts = withRouter((props: RouteComponentProps) => {
119125
<SidebarLayout>
120126
{pageContext?.subject
121127
? <SubjectSpecificConceptListSidebar {...sidebarProps}/>
122-
: <GenericConceptsSidebar {...sidebarProps}/>
128+
: <GenericConceptsSidebar {...sidebarProps} searchStages={searchStages} setSearchStages={setSearchStages} stageCounts={stageCounts}/>
123129
}
124130
<MainContent>
125131
{pageContext?.subject && <div className="d-flex align-items-baseline flex-wrap flex-md-nowrap">

0 commit comments

Comments
 (0)