72 lines
2.9 KiB
TypeScript
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>
|
|
)
|
|
}
|