diff --git a/backend/src/server.ts b/backend/src/server.ts index f6a2b74..7dd4a83 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -12,39 +12,60 @@ app.use(express.json()); let recipeList = recipes; // Routes +/** + * Load recipe list + * allows for filtering the list based on the recipes title using the query paramter search + */ app.get("/recipe", (req, res) => { - console.log("GET /recipe") - res.json(recipeList); + // extract search string from query parameters, convert to lower case for case insensitive search + let searchString : string = req.query.search ? req.query.search.toString().toLowerCase() : ""; + console.log("searchString", searchString) + let resultList : Recipe[] = [] + if(searchString && searchString !== ""){ + // if theres a search string filter recipe list based on title + // convert title to lower case in order to allow for case insensitive filtering + resultList = recipeList.filter(recipe => recipe.title.toLowerCase().includes(searchString)); + } else { + // if there is no search string simply return the entire list + resultList = recipeList; + } + res.json(resultList) }); +/** + * Load a single recipe by its id + */ app.get("/recipe/:id", (req, res) => { let recipeId : string = req.params.id; - console.log("GET /recipe/", recipeId); - const recipe = recipeList.find(r => r.id === req.params.id); - console.log(recipe ? "SUCCESS" : "404") + const recipe = recipeList.find(r => r.id === recipeId); recipe ? res.json(recipe) : res.status(404).send("Recipe not found"); }); +/** + * Create a new recipe + */ app.post("/recipe", (req, res) => { - console.log("POST /recipe") const newRecipe: Recipe = { id: uuidv4(), ...req.body }; recipeList.push(newRecipe); res.status(201).json(newRecipe); }); +/** + * Save an existing recipe + */ app.put("/recipe/:id", (req, res) => { let recipeId : string = req.params.id; - console.log("PUT /recipe/", recipeId) - const index = recipes.findIndex(r => r.id === recipeId); + const index = recipeList.findIndex(r => r.id === recipeId); if (index === -1) { - console.log("404") return res.status(404).send("Recipe not found"); } - console.log("SUCCESS"); recipeList[index] = { ...recipeList[index], ...req.body }; res.json(recipeList[index]); }); +/** + * Delete a recipe by id + */ app.delete("/recipe/:id", (req, res) => { recipeList = recipeList.filter(r => r.id !== req.params.id); res.status(204).send(); diff --git a/frontend/src/api/recipePoint.ts b/frontend/src/api/recipePoint.ts index 9b0f458..847b3a4 100644 --- a/frontend/src/api/recipePoint.ts +++ b/frontend/src/api/recipePoint.ts @@ -24,10 +24,17 @@ export async function fetchRecipe(id: string): Promise { /** * Load list of all recipes + * @param searchString Search string for filtering recipeList * @returns Array of recipe */ -export async function fetchRecipeList(): Promise { - const res = await fetch(`${RECIPE_URL}/`) +export async function fetchRecipeList(searchString : string): Promise { + let url : string = RECIPE_URL; + // if there's a search string add it as query parameter + if(searchString && searchString !== ""){ + url +="?search=" + searchString; + } + console.log("calling url", url) + const res = await fetch(url) if (!res.ok) { throw new Error(`Failed to fetch recipe list`) } diff --git a/frontend/src/components/recipes/RecipeListPage.tsx b/frontend/src/components/recipes/RecipeListPage.tsx index 4ba39ab..36c951c 100644 --- a/frontend/src/components/recipes/RecipeListPage.tsx +++ b/frontend/src/components/recipes/RecipeListPage.tsx @@ -3,7 +3,7 @@ import RecipeListItem from "./RecipeListItem" import type { Recipe } from "../../types/recipe" import { fetchRecipeList } from "../../api/recipePoint" import { useNavigate } from "react-router-dom" -import { getRecipeAddUrl, getRecipeAddUrlDefinition, getRecipeDetailUrl } from "../../routes" +import { getRecipeAddUrl, getRecipeDetailUrl } from "../../routes" /** * Displays a list of recipes in a sidebar layout. @@ -13,24 +13,27 @@ export default function RecipeListPage() { const navigate = useNavigate() const [recipeList, setRecipeList] = useState(null) - // load recipes once on render + const [searchString, setSearchString] = useState("") + // load recipes once on render and whenever search string changes + // @todo add delay. Only reload list if the search string hasn't changed for ~200 ms useEffect(() => { + console.log("loading recipe list with searchString", searchString) const loadRecipeList = async () => { try { // Fetch recipe list - console.log("loading recipe list") - const data = await fetchRecipeList() + const data = await fetchRecipeList(searchString) setRecipeList(data) } catch (err) { console.error(err) } } loadRecipeList() - }, []) + }, [,searchString]) const handleAdd = () => { navigate(getRecipeAddUrl()) } + if(!recipeList) { return
Unable to load recipes!
} return ( /*Contaier spanning entire screen used to center content horizontally */ @@ -44,6 +47,9 @@ export default function RecipeListPage() { { + setSearchString(e.target.value) + }} >