add search
This commit is contained in:
parent
f1a9b6d444
commit
df406b636e
3 changed files with 51 additions and 17 deletions
|
|
@ -12,39 +12,60 @@ app.use(express.json());
|
||||||
|
|
||||||
let recipeList = recipes;
|
let recipeList = recipes;
|
||||||
// Routes
|
// Routes
|
||||||
|
/**
|
||||||
|
* Load recipe list
|
||||||
|
* allows for filtering the list based on the recipes title using the query paramter search
|
||||||
|
*/
|
||||||
app.get("/recipe", (req, res) => {
|
app.get("/recipe", (req, res) => {
|
||||||
console.log("GET /recipe")
|
// extract search string from query parameters, convert to lower case for case insensitive search
|
||||||
res.json(recipeList);
|
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) => {
|
app.get("/recipe/:id", (req, res) => {
|
||||||
let recipeId : string = req.params.id;
|
let recipeId : string = req.params.id;
|
||||||
console.log("GET /recipe/", recipeId);
|
const recipe = recipeList.find(r => r.id === recipeId);
|
||||||
const recipe = recipeList.find(r => r.id === req.params.id);
|
|
||||||
console.log(recipe ? "SUCCESS" : "404")
|
|
||||||
recipe ? res.json(recipe) : res.status(404).send("Recipe not found");
|
recipe ? res.json(recipe) : res.status(404).send("Recipe not found");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new recipe
|
||||||
|
*/
|
||||||
app.post("/recipe", (req, res) => {
|
app.post("/recipe", (req, res) => {
|
||||||
console.log("POST /recipe")
|
|
||||||
const newRecipe: Recipe = { id: uuidv4(), ...req.body };
|
const newRecipe: Recipe = { id: uuidv4(), ...req.body };
|
||||||
recipeList.push(newRecipe);
|
recipeList.push(newRecipe);
|
||||||
res.status(201).json(newRecipe);
|
res.status(201).json(newRecipe);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an existing recipe
|
||||||
|
*/
|
||||||
app.put("/recipe/:id", (req, res) => {
|
app.put("/recipe/:id", (req, res) => {
|
||||||
let recipeId : string = req.params.id;
|
let recipeId : string = req.params.id;
|
||||||
console.log("PUT /recipe/", recipeId)
|
const index = recipeList.findIndex(r => r.id === recipeId);
|
||||||
const index = recipes.findIndex(r => r.id === recipeId);
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
console.log("404")
|
|
||||||
return res.status(404).send("Recipe not found");
|
return res.status(404).send("Recipe not found");
|
||||||
}
|
}
|
||||||
console.log("SUCCESS");
|
|
||||||
recipeList[index] = { ...recipeList[index], ...req.body };
|
recipeList[index] = { ...recipeList[index], ...req.body };
|
||||||
res.json(recipeList[index]);
|
res.json(recipeList[index]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a recipe by id
|
||||||
|
*/
|
||||||
app.delete("/recipe/:id", (req, res) => {
|
app.delete("/recipe/:id", (req, res) => {
|
||||||
recipeList = recipeList.filter(r => r.id !== req.params.id);
|
recipeList = recipeList.filter(r => r.id !== req.params.id);
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,17 @@ export async function fetchRecipe(id: string): Promise<Recipe> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load list of all recipes
|
* Load list of all recipes
|
||||||
|
* @param searchString Search string for filtering recipeList
|
||||||
* @returns Array of recipe
|
* @returns Array of recipe
|
||||||
*/
|
*/
|
||||||
export async function fetchRecipeList(): Promise<Recipe[]> {
|
export async function fetchRecipeList(searchString : string): Promise<Recipe[]> {
|
||||||
const res = await fetch(`${RECIPE_URL}/`)
|
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) {
|
if (!res.ok) {
|
||||||
throw new Error(`Failed to fetch recipe list`)
|
throw new Error(`Failed to fetch recipe list`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import RecipeListItem from "./RecipeListItem"
|
||||||
import type { Recipe } from "../../types/recipe"
|
import type { Recipe } from "../../types/recipe"
|
||||||
import { fetchRecipeList } from "../../api/recipePoint"
|
import { fetchRecipeList } from "../../api/recipePoint"
|
||||||
import { useNavigate } from "react-router-dom"
|
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.
|
* Displays a list of recipes in a sidebar layout.
|
||||||
|
|
@ -13,24 +13,27 @@ export default function RecipeListPage() {
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [recipeList, setRecipeList] = useState<Recipe[]|null>(null)
|
const [recipeList, setRecipeList] = useState<Recipe[]|null>(null)
|
||||||
// load recipes once on render
|
const [searchString, setSearchString] = useState<string>("")
|
||||||
|
// 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(() => {
|
useEffect(() => {
|
||||||
|
console.log("loading recipe list with searchString", searchString)
|
||||||
const loadRecipeList = async () => {
|
const loadRecipeList = async () => {
|
||||||
try {
|
try {
|
||||||
// Fetch recipe list
|
// Fetch recipe list
|
||||||
console.log("loading recipe list")
|
const data = await fetchRecipeList(searchString)
|
||||||
const data = await fetchRecipeList()
|
|
||||||
setRecipeList(data)
|
setRecipeList(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadRecipeList()
|
loadRecipeList()
|
||||||
}, [])
|
}, [,searchString])
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
navigate(getRecipeAddUrl())
|
navigate(getRecipeAddUrl())
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!recipeList) { return <div>Unable to load recipes!</div>}
|
if(!recipeList) { return <div>Unable to load recipes!</div>}
|
||||||
return (
|
return (
|
||||||
/*Contaier spanning entire screen used to center content horizontally */
|
/*Contaier spanning entire screen used to center content horizontally */
|
||||||
|
|
@ -44,6 +47,9 @@ export default function RecipeListPage() {
|
||||||
<label className="text-gray-500 w-2/3">{recipeList.length} Recipes</label>
|
<label className="text-gray-500 w-2/3">{recipeList.length} Recipes</label>
|
||||||
<input className="input-field"
|
<input className="input-field"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
|
onChange={e => {
|
||||||
|
setSearchString(e.target.value)
|
||||||
|
}}
|
||||||
></input>
|
></input>
|
||||||
<button className="primary-button"
|
<button className="primary-button"
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue