recipe-app/frontend/src/components/recipes/RecipeListPage.tsx
2025-10-25 08:00:59 +02:00

72 lines
2.9 KiB
TypeScript

import {useEffect, useState} from "react"
import RecipeListItem from "./RecipeListItem"
import type {RecipeModel} from "../../models/RecipeModel"
import {fetchRecipeList} from "../../api/points/CompactRecipePoint"
import {useNavigate} from "react-router-dom"
import {getRecipeAddUrl, getRecipeDetailUrl, getRecipeListUrl} from "../../routes"
import RecipeListToolbar from "./RecipeListToolbar"
/**
* Displays a list of recipes in a sidebar layout.
* Each recipe link fills the available width.
*/
export default function RecipeListPage() {
const navigate = useNavigate()
const [recipeList, setRecipeList] = useState<RecipeModel[] | null>(null)
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(() => {
console.log("loading recipe list with searchString", searchString)
const loadRecipeList = async () => {
try {
// Fetch recipe list
const data = await fetchRecipeList(searchString)
// @todo add and use compact recipe mapper
setRecipeList(data)
} catch (err) {
console.error(err)
}
}
loadRecipeList()
}, [searchString])
const handleAdd = () => {
navigate(getRecipeAddUrl())
}
if (!recipeList) {
return <div>Loading!</div>
}
return (
/*Container spanning entire screen used to center content horizontally */
<div className="app-bg">
{/* Container defining the maximum width of the content */}
<div className="content-bg">
{/* Header - remains in position when scrolling */}
<div className="sticky-header">
<h1>Recipes</h1>
<RecipeListToolbar
onAddClicked={handleAdd}
onSearchStringChanged={setSearchString}
numberOfRecipes={recipeList.length}
/>
</div>
{/*Content - List of recipe cards */}
<div className="w-full pt-4">
<div
className="grid gap-6 grid-cols-[repeat(auto-fit,minmax(220px,auto))] max-w-6xl mx-auto justify-center">
{recipeList.map((recipe) => (
<RecipeListItem
key={recipe.id}
title={recipe.title}
targetPath={recipe.id !== undefined ? getRecipeDetailUrl(recipe.id) : getRecipeListUrl()} // @todo proper error handling
/>
))}
</div>
</div>
</div>
</div>
)
}