feat(nutrition): filter ingredient search by Nutri-Score#1237
Conversation
Add a Nutri-Score filter to the ingredient autocompleter. When enabled, a Material-UI slider lets the user pick the worst acceptable grade (A–E); the value is sent to the backend as `nutriscore__lte` so that only ingredients at or better than that grade are returned. * refactor `searchIngredient` to accept an `IngredientSearchFilters` object instead of five positional parameters — this makes it safe to add further optional filters without shifting call sites * persist the toggle state and selected grade in localStorage using the same pattern as the existing vegan / vegetarian / language filters * add 5 tests covering slider visibility, localStorage round-trip, and the resulting API call shape; clear localStorage in beforeEach to prevent leakage between tests Depends on wger-project/wger PR #2305 which adds the `nutriscore__lte` lookup to the backend filterset. Refs #2295
|
I'm thinking if we could remove the toggle completely by adding an option to the slider (something like "off" or "no filter") that turned off the search |
Addresses review feedback on wger-project#1237 — the Switch + conditional Slider combination expressed one semantic decision ("what grade ceiling?") through two controls. Folded "Off" into the slider itself: - Slider stops are now Off | A | B | C | D | E (was A..E, gated by Switch). - Off position sends no `nutriscore__lte`; A..E map as before. - Heading changed from "Worst acceptable Nutri-Score" to "Nutri-Score filter", with a dynamic helper below ("No filter" / "<grade> or better") that mirrors aria-valuetext for screen-reader parity. - Storage collapsed: removed STORAGE_KEY_NUTRISCORE_ENABLED; STORAGE_KEY_NUTRISCORE_MAX is absent when the slider is at Off. Tests updated: 14/14 pass.
Thanks for the review, @rolandgeider — applied your suggestion in the follow-up commit. The slider expresses the "better than X" semantic cleanly. "Only X" is only incidentally supported at A (since A is the top grade). If exact-match filtering becomes a product requirement later, happy to follow up with a secondary control (chips or a mode selector) — but the backend's |
|
awesome, this looks much cleaner |
Mirrors the React refactor in wger-project/react#1237 addressing Roland's review — the SwitchListTile + conditional Slider pair expressed one semantic decision ("what grade ceiling?") through two controls. Folded "Off" into the slider itself: - Slider stops are now Off | A | B | C | D | E (was A..E, gated by a SwitchListTile). - Off position sends no `nutriscoreMax`; A..E map as before. - Heading changed from "Filter by Nutri-Score" to "Nutri-Score filter", with a dynamic helper ("No filter" / "<grade> or better"). - Model: `IngredientFilters.nutriscoreMax` is now nullable; removed `filterNutriscore`. `copyWith` takes a `clearNutriscoreMax` flag because Dart's `??` cannot distinguish "no argument" from "null". - Storage collapsed: `saveIngredientNutriscoreMax(null)` removes the key; `getIngredientNutriscoreMax()` returns `null` when absent. - Riverpod notifier: removed `toggleNutriscore`; `chooseNutriscoreMax` now takes `NutriScore?`. - l10n: removed `filterNutriscoreMax`; added `filterNutriscoreOff`, `filterNutriscoreNoFilter`, `filterNutriscoreOrBetter` (placeholder). Tests updated: 61/61 pass.
|
merged, thank you! (we probably don't need any refinements on these filters in the future, realistically I don't think anybody is going to want to search for "only A or C" or "worse than C") |


Summary
Adds a Nutri-Score filter to the ingredient autocompleter, as requested in wger-project/wger#2295. Users can now narrow search results to ingredients graded at or better than a chosen Nutri-Score (A–E).
When the filter is enabled, the selected "worst acceptable grade" is sent to the backend as
nutriscore__lte=<grade>. The toggle state and grade are persisted inlocalStorage, matching the existing vegan / vegetarian / language filter pattern.Changes
src/services/ingredient.ts— refactoredsearchIngredientfrom five positional parameters to(name, filters: IngredientSearchFilters). This keeps future optional filters from shifting call sites. AddsnutriscoreMaxsupport.src/components/Nutrition/widgets/IngredientAutcompleter.tsx— new Switch + MUI Slider (A/B/C/D/E marks) inside the filter popover. Defaults to C; hidden until the switch is enabled; persisted tolocalStorage.src/components/Nutrition/widgets/IngredientAutocompleter.test.tsx— 5 new tests covering slider visibility, localStorage round-trip, and the resultingsearchIngredientcall shape. AddedlocalStorage.clear()tobeforeEachto prevent cross-test leakage.public/locales/en/translation.json— two new i18n keys:nutrition.filterNutriscore,nutrition.filterNutriscoreMax.UI
Inside the existing filter popover (revealed by the tune icon next to the search input), below the Vegan / Vegetarian switches:
The slider is hidden until the "Filter by Nutri-Score" switch is turned on. Screenshots are available on request (or reproducible by mounting
IngredientSearchagainst a local dev server).Depends on
nutriscore__ltelookup to the Django filterset. Must be merged and released before this PR is useful in production; the UI still renders correctly against older backends but the server will ignore the unknown query parameter.Test plan
jest src/components/Nutrition/widgets/IngredientAutocompleter.test.tsx→ 13/13 passjest src/services→ all passlocalStoragenutriscore__lteappears in the outgoing request when the filter is on and is omitted when offRelated