Skip to content

Commit 8b1a60c

Browse files
authored
Merge pull request #3332 from yujiiroo/humble-devel
Save search bar and filter state using sessionStorage (fixes #3257)
2 parents 02a8091 + d50d7de commit 8b1a60c

File tree

2 files changed

+138
-22
lines changed

2 files changed

+138
-22
lines changed

react_frontend/src/components/buttons/Search.tsx

Lines changed: 128 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ import {
1111
MenuItem,
1212
} from "@mui/material";
1313
import { useHomepage } from "Contexts/HomepageContext";
14-
import { useState } from "react";
14+
import { useState, useEffect } from "react";
1515
import { useAcademyTheme } from "Contexts/AcademyThemeContext";
16+
import { Filters } from "src/types/exercises";
17+
18+
// SessionStorage keys
19+
const FILTER_STORAGE_KEY = "ra_home_filters_v1";
20+
const SEARCH_STORAGE_KEY = "ra_home_search_v1";
1621

1722
// Estilos
1823
const Search = styled("div")(({ roundness }: { roundness: number }) => ({
@@ -85,13 +90,67 @@ const StyledMenu = styled(Menu)(
8590
})
8691
);
8792

88-
// Componente FilterMenu
93+
// Filter persistence structure
94+
type FilterState = {
95+
tags: boolean;
96+
description: boolean;
97+
status: boolean;
98+
};
99+
100+
const DEFAULT_FILTER_STATE: FilterState = {
101+
tags: true,
102+
description: false,
103+
status: false,
104+
};
105+
106+
const filtersFromState = (state: FilterState): Filters[] => {
107+
const list: Filters[] = ["name"]; // name is always active
108+
if (state.tags) list.push("tags");
109+
if (state.description) list.push("description");
110+
if (state.status) list.push("status");
111+
return list;
112+
};
113+
89114
const FilterMenu = () => {
90-
const { appendFilterItem } = useHomepage();
115+
const { appendFilterItem, setFilterItemsList } = useHomepage();
91116
const theme = useAcademyTheme();
92117
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
93118
const open: boolean = Boolean(anchorEl);
94119

120+
const [filterState, setFilterState] =
121+
useState<FilterState>(DEFAULT_FILTER_STATE);
122+
123+
// Restore session filter state
124+
useEffect(() => {
125+
if (typeof window === "undefined") return;
126+
127+
try {
128+
const raw = window.sessionStorage.getItem(FILTER_STORAGE_KEY);
129+
if (!raw) return;
130+
131+
const parsed = JSON.parse(raw) as Partial<FilterState>;
132+
const persisted: FilterState = {
133+
tags:
134+
typeof parsed.tags === "boolean"
135+
? parsed.tags
136+
: DEFAULT_FILTER_STATE.tags,
137+
description:
138+
typeof parsed.description === "boolean"
139+
? parsed.description
140+
: DEFAULT_FILTER_STATE.description,
141+
status:
142+
typeof parsed.status === "boolean"
143+
? parsed.status
144+
: DEFAULT_FILTER_STATE.status,
145+
};
146+
147+
setFilterState(persisted);
148+
setFilterItemsList(filtersFromState(persisted));
149+
} catch (err) {
150+
console.error("Failed to restore filter state", err);
151+
}
152+
}, [setFilterItemsList]);
153+
95154
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
96155
setAnchorEl(event.currentTarget);
97156
};
@@ -100,9 +159,31 @@ const FilterMenu = () => {
100159
setAnchorEl(null);
101160
};
102161

103-
const handleFilterList = (e: any) => {
104-
const item = e.target.name;
105-
appendFilterItem(item);
162+
// Toggle and persist filter values
163+
const handleFilterCheckboxChange = (
164+
e: React.ChangeEvent<HTMLInputElement>
165+
) => {
166+
const name = e.target.name as keyof FilterState;
167+
168+
setFilterState((prev) => {
169+
const updated: FilterState = {
170+
...prev,
171+
[name]: !prev[name],
172+
};
173+
174+
try {
175+
window.sessionStorage.setItem(
176+
FILTER_STORAGE_KEY,
177+
JSON.stringify(updated)
178+
);
179+
} catch (err) {
180+
console.error("Failed to save filter state", err);
181+
}
182+
183+
return updated;
184+
});
185+
186+
appendFilterItem(name as Filters);
106187
};
107188

108189
return (
@@ -127,44 +208,77 @@ const FilterMenu = () => {
127208
checkboxColor={theme.palette.text!}
128209
roundness={theme.roundness!}
129210
>
211+
{/* Name always active and not persisted */}
130212
<MenuItem>
131213
<Checkbox defaultChecked disabled size="small" name="name" />
132214
Name
133215
</MenuItem>
134216
<MenuItem>
135217
<Checkbox
136-
defaultChecked
137218
size="small"
138-
onClick={handleFilterList}
139219
name="tags"
220+
checked={filterState.tags}
221+
onChange={handleFilterCheckboxChange}
140222
/>
141223
Tags
142224
</MenuItem>
143225
<MenuItem>
144226
<Checkbox
145227
size="small"
146-
onClick={handleFilterList}
147228
name="description"
229+
checked={filterState.description}
230+
onChange={handleFilterCheckboxChange}
148231
/>
149232
Description
150233
</MenuItem>
151234
<MenuItem>
152-
<Checkbox size="small" onClick={handleFilterList} name="status" />
235+
<Checkbox
236+
size="small"
237+
name="status"
238+
checked={filterState.status}
239+
onChange={handleFilterCheckboxChange}
240+
/>
153241
Status
154242
</MenuItem>
155243
</StyledMenu>
156244
</>
157245
);
158246
};
159247

160-
// Componente principal SearchBar
161248
const SearchBar = () => {
162249
const { setSearchBarText } = useHomepage();
163250
const theme = useAcademyTheme();
164251

252+
const [searchValue, setSearchValue] = useState<string>("");
253+
254+
// Restore session search text
255+
useEffect(() => {
256+
if (typeof window === "undefined") return;
257+
258+
try {
259+
const saved = window.sessionStorage.getItem(SEARCH_STORAGE_KEY);
260+
if (saved) {
261+
setSearchValue(saved);
262+
setSearchBarText(saved.toLowerCase());
263+
}
264+
} catch (err) {
265+
console.error("Failed to restore search text", err);
266+
}
267+
}, [setSearchBarText]);
268+
269+
// Update sessionStorage + context
165270
const inputHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
166-
const lowerCase = e.target.value.toLowerCase();
271+
const value = e.target.value;
272+
setSearchValue(value);
273+
274+
const lowerCase = value.toLowerCase();
167275
setSearchBarText(lowerCase);
276+
277+
try {
278+
window.sessionStorage.setItem(SEARCH_STORAGE_KEY, value);
279+
} catch (err) {
280+
console.error("Failed to save search text", err);
281+
}
168282
};
169283

170284
return (
@@ -181,6 +295,7 @@ const SearchBar = () => {
181295
</SearchIconWrapper>
182296
<StyledInputBase
183297
placeholder="Search…"
298+
value={searchValue}
184299
onChange={inputHandler}
185300
textColor={theme.palette.text!}
186301
inputProps={{ "aria-label": "search" }}
@@ -192,3 +307,4 @@ const SearchBar = () => {
192307
};
193308

194309
export default SearchBar;
310+

react_frontend/src/contexts/HomepageContext.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ export interface HomepageContextType {
77
setSearchBarText: (text: string) => void;
88
appendFilterItem: (item: Filters) => void;
99
getFilterItemsList: () => Filters[];
10+
setFilterItemsList: (items: Filters[]) => void;
1011
}
1112

1213
const HomepageContext = createContext<HomepageContextType>({
13-
getSearchBarText: () => {
14-
return "";
15-
},
14+
getSearchBarText: () => "",
1615
setSearchBarText: () => {},
1716
appendFilterItem: () => {},
18-
getFilterItemsList: () => {
19-
return [];
20-
},
17+
getFilterItemsList: () => [],
18+
setFilterItemsList: () => {},
2119
});
2220

2321
export const useHomepage = () => useContext(HomepageContext);
@@ -34,12 +32,12 @@ export function HomepageProvider({ children }: { children?: ReactNode }) {
3432
const setSearchBarText = (text: string) => {
3533
setInputText(text);
3634
};
35+
3736
const getFilterItemsList = () => filterItemsList;
37+
3838
const appendFilterItem = (item: Filters) => {
39-
setFilterItemsList(
40-
filterItemsList.includes(item)
41-
? filterItemsList.filter((i) => i !== item)
42-
: [...filterItemsList, item]
39+
setFilterItemsList((prev) =>
40+
prev.includes(item) ? prev.filter((i) => i !== item) : [...prev, item]
4341
);
4442
};
4543

@@ -50,6 +48,7 @@ export function HomepageProvider({ children }: { children?: ReactNode }) {
5048
setSearchBarText,
5149
appendFilterItem,
5250
getFilterItemsList,
51+
setFilterItemsList,
5352
}}
5453
>
5554
{children}
@@ -58,3 +57,4 @@ export function HomepageProvider({ children }: { children?: ReactNode }) {
5857
}
5958

6059
export default HomepageContext;
60+

0 commit comments

Comments
 (0)