diff --git a/package.json b/package.json index 52254b0..3710004 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@fontsource/roboto": "^5.0.0", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.1", + "fuse.js": "^6.6.2", "gh-pages": "^5.0.0", "i18next": "^23.2.3", "i18next-browser-languagedetector": "^7.1.0", diff --git a/src/App.tsx b/src/App.tsx index 5101eea..7897427 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,40 +1,45 @@ import type { FoodList } from "./types/food"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import { currentMonth } from "./utils/utils"; import fetchData from "./loadData"; +import { FoodDBProvider } from "./contexts/FoodDB"; import FoodOfTheMonth from "./routes/FoodOfTheMonth"; import FoodPage from "./routes/FoodPage"; import Layout from "./routes/Layout"; import { NotFound } from "./routes/NotFound"; export default function App() { - const [food, setFood] = useState([] as FoodList); + const [ food, setFood ] = useState([] as FoodList) - if (food.length === 0) fetchData(setFood, `ITALIA-fruits-and-veggies.csv`); + useEffect(() => { + fetchData(setFood, "ITALIA-fruits-and-veggies.csv") + },[]) return food.length > 0 ? ( - -
- - } /> - }> - } - /> - } - /> - } - /> - - -
-
+ + +
+ + } /> + }> + } + /> + } + /> + } + /> + + +
+
+
) : (
not loaded
); diff --git a/src/components/HeaderBar.tsx b/src/components/HeaderBar.tsx index 193f94a..7b51e8c 100644 --- a/src/components/HeaderBar.tsx +++ b/src/components/HeaderBar.tsx @@ -1,6 +1,5 @@ -import type { FoodList } from "../types/food"; - -import { useRef, useEffect } from "react"; +import type { FoodList, FoodObject } from "../types/food"; +import i18next from "i18next"; import { AppBar, styled, @@ -13,6 +12,8 @@ import MenuIcon from "@mui/icons-material/Menu"; import SearchIcon from "@mui/icons-material/Search"; import { useTranslation } from "react-i18next"; import { ArrowBackIosNew } from "@mui/icons-material"; +import Fuse from 'fuse.js' +import { useRef, useEffect } from "react" import { useNavigate, useLocation } from "react-router-dom"; type Props = { @@ -42,11 +43,22 @@ export default function HeaderBar(props: Props) { if (query.current.value === "") { return food; } - return food.filter((item) => { - return item.description[0].name - .toLowerCase() - .includes(query.current.value.trim().toLowerCase()); - }); + + // TODO: Load index pregenerated + const options = { + threshold: 0.3, + keys: [ + { name: 'name-en', getFn: (food:FoodObject) => food.description[0].name }, + { name: 'name-it', getFn: (food:FoodObject) => food.description[1].name } + ] + } + + const fuse = new Fuse(food, options) + const searchText = query.current.value.trim().toLowerCase() + const searchLanguage:{[lang:string]:string} = {} + searchLanguage["name-" + i18next.language] = searchText + + return fuse.search(searchLanguage).map((i) => i.item) }; const Search = styled("div")(({ theme }) => ({ diff --git a/src/components/RenderFoods.tsx b/src/components/RenderFoods.tsx index beca466..9c9626c 100644 --- a/src/components/RenderFoods.tsx +++ b/src/components/RenderFoods.tsx @@ -1,4 +1,3 @@ - import type { FoodList } from "../types/food"; import Item from "./Item"; import { Box, Stack } from "@mui/material"; @@ -8,6 +7,10 @@ interface RenderFoodProps { foodList: FoodList } +interface RenderFoodProps { + foodList: FoodList +} + //render the grid of foods const RenderFoods:FunctionComponent = (props:RenderFoodProps) => { const foodList = props.foodList diff --git a/src/contexts/FoodDB/index.tsx b/src/contexts/FoodDB/index.tsx new file mode 100644 index 0000000..76e0389 --- /dev/null +++ b/src/contexts/FoodDB/index.tsx @@ -0,0 +1,21 @@ +import React from "react" +import { Dispatch } from "react" +import { reducer } from "./reducer" +import { FoodList } from "../../types/food" + + +export const FoodDBContext = React.createContext<[FoodList, Dispatch]>([ + [] as FoodList, + () => null +]) + + +export const FoodDBProvider = ({ children, initialState }:{ children: React.ReactNode, initialState: FoodList }) => { + const [state, dispatch] = React.useReducer(reducer, initialState) + + return ( + + { children } + + ) +} diff --git a/src/contexts/FoodDB/reducer.tsx b/src/contexts/FoodDB/reducer.tsx new file mode 100644 index 0000000..ab40d89 --- /dev/null +++ b/src/contexts/FoodDB/reducer.tsx @@ -0,0 +1,17 @@ +import { FoodList } from "../../types/food" + +export interface IFoodDBState { + foodDB: FoodList +} + +export const reducer:React.Reducer = (state, action) => { + switch (action.type) { + case "initDB": + return { ...state, foodDB: action.payload }; + + default: + return state + } +} + +export const initialState = { foodDB: {} } diff --git a/src/routes/FoodOfTheMonth.tsx b/src/routes/FoodOfTheMonth.tsx index 1897bc7..ca1d648 100644 --- a/src/routes/FoodOfTheMonth.tsx +++ b/src/routes/FoodOfTheMonth.tsx @@ -14,8 +14,12 @@ import { import { ArrowLeft, ArrowRight } from "@mui/icons-material"; import RenderFoods from "../components/RenderFoods"; import { useTranslation } from "react-i18next"; +import { FoodDBContext } from "../contexts/FoodDB" +import React from "react" + export default function FoodOfTheMonth({ food }: { food: FoodList }) { + const [food, _] = React.useContext(FoodDBContext) const { selectedMonthNum } = useParams(); const { t } = useTranslation(); const monthNum = Number(selectedMonthNum) - 1; @@ -24,6 +28,7 @@ export default function FoodOfTheMonth({ food }: { food: FoodList }) { Veggies: [], }; + //month change arrows function const navigate = useNavigate(); useEffect(() => { @@ -37,6 +42,7 @@ export default function FoodOfTheMonth({ food }: { food: FoodList }) { if (item.season[monthNum] === true) monthFood.push(item); }); + //filters the fruits and vegetables const filterFoodType = (monthFood: FoodList, foodCategory: FoodCategory) => monthFood.filter((item) => item.category === foodCategory); @@ -106,6 +112,7 @@ export default function FoodOfTheMonth({ food }: { food: FoodList }) { /> + ); } diff --git a/src/routes/FoodPage.tsx b/src/routes/FoodPage.tsx index 24690bb..c26d66c 100644 --- a/src/routes/FoodPage.tsx +++ b/src/routes/FoodPage.tsx @@ -1,12 +1,14 @@ -import type { FoodList } from "../types/food"; import { currentMonth } from "../utils/utils"; import { useParams, useNavigate, Link } from "react-router-dom"; import { useEffect } from "react"; import { Box, Typography, Stack } from "@mui/material"; import { styled } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; +import { FoodDBContext } from "../contexts/FoodDB" +import React from "react" -export default function FoodPage({ food }: { food: FoodList }) { +export default function FoodPage() { + const [ food, _ ] = React.useContext(FoodDBContext) const { id } = useParams(); const { t } = useTranslation(); diff --git a/yarn.lock b/yarn.lock index a3849df..152fd30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2343,6 +2343,11 @@ function-bind@^1.1.1: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +fuse.js@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111" + integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"