diff --git a/client/src/components/derived/IngredientSelector.tsx b/client/src/components/derived/IngredientSelector.tsx index 49f3131..c7cbf5a 100644 --- a/client/src/components/derived/IngredientSelector.tsx +++ b/client/src/components/derived/IngredientSelector.tsx @@ -1,5 +1,6 @@ import { Autocomplete, TextField } from "@mui/material" -import { useEffect, useRef, useState } from "react"; +import { ChangeEvent, useEffect, useRef, useState } from "react"; +import { useAuthContext } from "../../context/AuthContext"; import { DropdownData, IIngredient } from "../../schemas"; import { Button } from "../ui"; @@ -10,17 +11,43 @@ interface IngredientSelectorProps { destroy: (position: number) => void } +interface RowState { + quantity: number + measurement: string | null + ingredientSelection: string | null + ingredients: IIngredient[] +} + +const createIngredient = (name: string, userid: string | number) => { + return { + name: name, + createdbyid: userid + } as IIngredient +} + function IngredientSelector({ position, ingredients, units, destroy }: IngredientSelectorProps) { - const [options, setOptions] = useState(ingredients.map(each => each.name)); + const { user } = useAuthContext(); + + const [ingredientOptions, setIngredientOptions] = useState(ingredients.map(each => each.name)); const [measurementUnits, setMeasurementUnits] = useState(units.map(each => each.name)); - const [newOptions, setNewOptions] = useState(new Array()); - const [selected, setSelected] = useState(new Array()); + + const [rowState, setRowState] = useState({ + quantity: 0, + measurement: null, + ingredientSelection: null, + ingredients: ingredients + }) + + useEffect(() => { + console.log("Row " + position + " state changed:"); + console.log(rowState); + }, [rowState]) return (
- + setRowState({...rowState, quantity: parseFloat(e.target.value) })} /> )} - onKeyDown={(e) => { - console.log(e.code); - /* if (e.code == 'Enter') { - const inputVal: string = e.target['value' as keyof EventTarget].toString(); - if (inputVal.length) { - setSelected(prev => [...prev, inputVal]) - setOptions((prev) => [...prev, inputVal]); - } - } */ + onChange={(event, value) => { + setRowState({ ...rowState, measurement: value }); }} /> ( )} - onKeyDown={(e) => { - if (e.code == 'Enter') { - const inputVal: string = e.target['value' as keyof EventTarget].toString(); - if (inputVal.length) { - setSelected(prev => [...prev, inputVal]) - setOptions((prev) => [...prev, inputVal]); - } + onChange={(event, newValue) => { + if (!user) return; + + if (typeof newValue == 'string') { + const newIngredient = createIngredient(newValue, user.id!); + setIngredientOptions((prev) => [...prev, newValue]); + + setRowState((prev) => { + let shouldInsert = true; + for (let each of prev.ingredients) { + if (each.name == newValue) shouldInsert = false; + } + + return { + ...prev, + ingredients: (shouldInsert ? [...prev.ingredients, newIngredient] : [...prev.ingredients]), + ingredientSelection: newValue + } + }) + } else if (newValue == null) { + setRowState((prev) => { + return { + ...prev, + ingredients: ingredients, + ingredientSelection: null + } + }) } }} + /> diff --git a/client/src/components/pages/AddRecipe.tsx b/client/src/components/pages/AddRecipe.tsx index dd4dc5a..f5bcb1c 100644 --- a/client/src/components/pages/AddRecipe.tsx +++ b/client/src/components/pages/AddRecipe.tsx @@ -12,15 +12,15 @@ import { Autocomplete, TextField } from "@mui/material"; const AddRecipe = () => { const { user, token } = useAuthContext(); - const { data, setData } = useSelectorContext(); // received recipe data const [input, setInput] = useState({ name: '', preptime: '', description: '', authoruserid: '' }) // UI state handling + const [ingredients, setIngredients] = useState([]); const [measurements, setMeasurements] = useState([]); - const [ingredientFields, setIngredientFields] = useState>([]); const [courseData, setCourseData] = useState([]); + const [ingredientFields, setIngredientFields] = useState>([]); const [optionCount, setOptionCount] = useState(0); // status reporting @@ -31,12 +31,12 @@ const AddRecipe = () => { token && (async() => { const ingredients = new API.Ingredient(token); const _dropdowns = new API.Dropdowns(token); - const result = await ingredients.getAll(); + const ingredientList = await ingredients.getAll(); const measurementList = await _dropdowns.getAllMeasurements(); const courseList = await _dropdowns.getAllCourses(); - if (result) { - setData((prev) => [...prev, ...result]); + if (ingredientList) { + setIngredients((prev) => [...prev, ...ingredientList]); } if (measurementList) { @@ -50,11 +50,11 @@ const AddRecipe = () => { }, [token]) useEffect(() => { - if (data.length && measurements.length) { - setIngredientFields([]); + if (ingredients.length && measurements.length) { + setIngredientFields([]); } - }, [data, measurements]) + }, [ingredients, measurements]) // once user information is available, store it in recipe data useEffect(() => { @@ -69,7 +69,7 @@ const AddRecipe = () => { // submit handler const handleCreate = async () => { if (!token) return; - + for (let field of Object.keys(input)) { if (!input[field as keyof IRecipe]) { return; @@ -107,7 +107,7 @@ const AddRecipe = () => { }, [ingredientFields]); function handleNewOption() { - setIngredientFields((prev) => [...prev, ]) + setIngredientFields((prev) => [...prev, ]) setOptionCount(prev => prev + 1); } @@ -144,7 +144,7 @@ const AddRecipe = () => { />} - { data && ( + { ingredients && ( <> diff --git a/client/src/components/pages/Recipe.tsx b/client/src/components/pages/Recipe.tsx index c65eb3a..fc950cb 100644 --- a/client/src/components/pages/Recipe.tsx +++ b/client/src/components/pages/Recipe.tsx @@ -1,37 +1,92 @@ import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; -import { Page, Panel } from "../ui"; -import { IRecipe } from "../../schemas"; +import { Divider, Page, Panel } from "../ui"; +import { IRecipe, IUser, IIngredient } from "../../schemas"; import { getRecipeByID } from "../../util/apiUtils"; import Protect from "../../util/Protect"; +import API from "../../util/API"; +import { useAuthContext } from "../../context/AuthContext"; +import ResourceNotFound from "./StatusPages/404"; export default function Recipe() { - const [recipe, setRecipe] = useState(); + const { user, token } = useAuthContext(); const { id } = useParams(); - if (!id) { - return ( - -

404 | Not Found

-

There's no content here! Technically, you shouldn't have even been able to get here.

-

So, kudos, I guess!

-
- ) - } + const [recipe, setRecipe] = useState(); + const [userData, setUserData] = useState(); + const [ingredientData, setIngredientData] = useState([]); + const [view, setView] = useState(

Loading...

); useEffect(() => { - getRecipeByID(id); - }, []) + if (token && id) { + (async() => { + const recipeAPI = new API.Recipe(token); + const result = await recipeAPI.getByID(id); + if (result) { + setRecipe(result); + } else { + setRecipe("no recipe"); + } + })() + } + }, [token]) - return ( - - { recipe && ( - + useEffect(() => { + if (recipe) { + if (recipe === "no recipe") { + setView(

We couldn't find a recipe with the ID {id}.

); + } else { + if (!user || !token) return; + + (async() => { + const ingredientAPI = new API.Ingredient(token); + const result = await ingredientAPI.getAllForRecipe(id!); + if (result.length) setIngredientData(result); + }) + + const selfAuthored = (recipe.authoruserid == user.id!); + if (selfAuthored) { + setUserData(user); + } else { + (async() => { + const userAPI = new API.User(token); + const foundUser = await userAPI.getByID(recipe.authoruserid as string); + setUserData(foundUser); + })(); + } + } + } + }, [recipe]) + + useEffect(() => { + if (!userData) return; + + if (recipe && recipe !== "no recipe") { + setView( +

{recipe.name}

-

{recipe.description}

-

{recipe.preptime}

-
- )} -
- ) +

Provided courtesy of {userData.firstname} {userData.lastname}

+ + + +

Prep time: {recipe.preptime}

+ + + +

Ingredients:

+ { ingredientData.length + ? ingredientData.map((each: IIngredient) =>

{each.name}

) + : "No ingredients for this recipe" + } + + + +
+ + + ); + } + }, [userData, ingredientData]); + + return view } \ No newline at end of file diff --git a/client/src/util/API.ts b/client/src/util/API.ts index b97ae03..1e8c8bc 100644 --- a/client/src/util/API.ts +++ b/client/src/util/API.ts @@ -198,6 +198,11 @@ module API { const response = await this.instance.post(this.endpoint + `/${ingredientID}?recipeID=${recipeID}`, this.headers); return Promise.resolve(response.data); } + + async getAllForRecipe(recipeID: string | number) { + const response = await this.instance.get(this.endpoint + `?recipeID=${recipeID}`, this.headers); + return Promise.resolve(response.data); + } } export class Collection extends RestController {