Skip to content

Commit 45925e1

Browse files
committed
Search and filter skill categories
1 parent 0ddd148 commit 45925e1

File tree

2 files changed

+86
-9
lines changed

2 files changed

+86
-9
lines changed

web-ui/src/pages/SkillCategoriesPage.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,12 @@
33
flex-direction: row;
44
justify-content: space-between;
55
margin-bottom: 1rem;
6+
flex-wrap: wrap;
7+
}
8+
9+
.skill-categories-actions {
10+
display: flex;
11+
flex-direction: row;
12+
gap: 1rem;
13+
align-items: center;
614
}

web-ui/src/pages/SkillCategoriesPage.jsx

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ import { styled } from '@mui/material/styles';
33

44
import { AppContext } from "../context/AppContext";
55

6-
import {Button, DialogActions, DialogContent, DialogContentText, DialogTitle, Typography} from "@mui/material";
6+
import {
7+
Button,
8+
DialogActions,
9+
DialogContent,
10+
DialogContentText,
11+
DialogTitle,
12+
TextField,
13+
Typography
14+
} from "@mui/material";
715
import SkillCategoryCard from "../components/skill-category-card/SkillCategoryCard";
816

917
import "./SkillCategoriesPage.css";
10-
import {selectCsrfToken} from "../context/selectors";
18+
import {selectCsrfToken, selectOrderedSkills} from "../context/selectors";
1119
import {
1220
createSkillCategory,
1321
deleteSkillCategory,
@@ -16,6 +24,9 @@ import {
1624
import SkillCategoryNewDialog from "../components/skill-category-new-dialog/SkillCategoryNewDialog";
1725
import {UPDATE_TOAST} from "../context/actions";
1826
import Dialog from "@mui/material/Dialog";
27+
import InputAdornment from "@mui/material/InputAdornment";
28+
import {Search} from "@mui/icons-material";
29+
import Autocomplete from "@mui/material/Autocomplete";
1930

2031
const PREFIX = 'SkillCategoriesPage';
2132
const classes = {
@@ -40,10 +51,13 @@ const Root = styled('div')({
4051
const SkillCategoriesPage = () => {
4152
const { state, dispatch } = useContext(AppContext);
4253
const csrf = selectCsrfToken(state);
54+
const skills = selectOrderedSkills(state);
4355

4456
const [skillCategories, setSkillCategories] = useState([]);
4557
const [dialogOpen, setDialogOpen] = useState(false);
4658
const [categoryToDelete, setCategoryToDelete] = useState(null);
59+
const [query, setQuery] = useState("");
60+
const [skillFilter, setSkillFilter] = useState(null);
4761

4862
const retrieveCategories = useCallback(async () => {
4963
if (csrf) {
@@ -96,18 +110,73 @@ const SkillCategoriesPage = () => {
96110
}
97111
}
98112

113+
const getFilteredCategories = useCallback(() => {
114+
if (skillCategories) {
115+
return skillCategories.filter(category => {
116+
let nameMatches = true;
117+
if (query) {
118+
const sanitizedQuery = query.toLowerCase().trim();
119+
nameMatches = category.name.toLowerCase().includes(sanitizedQuery);
120+
}
121+
122+
let skillMatches = true;
123+
if (skillFilter) {
124+
skillMatches = category.skills.find(skill => skill.name === skillFilter.name);
125+
}
126+
127+
return nameMatches && skillMatches;
128+
});
129+
}
130+
131+
return [];
132+
}, [skillCategories, query, skillFilter]);
133+
99134
return (
100135
<Root className={classes.root}>
101136
<div className="skill-categories-header">
102137
<Typography variant="h4">Skill Categories</Typography>
103-
<Button
104-
variant="contained"
105-
onClick={() => setDialogOpen(true)}
106-
>
107-
New Category
108-
</Button>
138+
<div className="skill-categories-actions">
139+
<TextField
140+
style={{ minWidth: "200px" }}
141+
label="Search"
142+
fullWidth
143+
placeholder="Category name"
144+
variant="outlined"
145+
size="small"
146+
value={query}
147+
onChange={(event) => setQuery(event.target.value)}
148+
InputProps={{
149+
endAdornment: <InputAdornment position="end" color="gray"><Search/></InputAdornment>
150+
}}
151+
/>
152+
<Autocomplete
153+
renderInput={(params) => (
154+
<TextField
155+
{...params}
156+
style={{ minWidth: "200px" }}
157+
label="Filter by Skill"
158+
variant="outlined"
159+
size="small"
160+
placeholder="Skill name"
161+
fullWidth
162+
/>
163+
)}
164+
options={skills}
165+
getOptionLabel={(option) => option.name}
166+
filterSelectedOptions
167+
value={skillFilter}
168+
onChange={(_, newValue) => setSkillFilter(newValue)}
169+
/>
170+
<Button
171+
style={{ width: "300px" }}
172+
variant="contained"
173+
onClick={() => setDialogOpen(true)}
174+
>
175+
New Category
176+
</Button>
177+
</div>
109178
</div>
110-
{skillCategories.map(category =>
179+
{getFilteredCategories().map(category =>
111180
<SkillCategoryCard
112181
key={category.id}
113182
id={category.id}

0 commit comments

Comments
 (0)