reworking view recipe by id

This commit is contained in:
Mikayla Dobson
2023-02-18 13:48:11 -06:00
parent 6d4ebd7757
commit 949762f3a0
4 changed files with 159 additions and 57 deletions

View File

@@ -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<string>());
const [selected, setSelected] = useState(new Array<string>());
const [rowState, setRowState] = useState<RowState>({
quantity: 0,
measurement: null,
ingredientSelection: null,
ingredients: ingredients
})
useEffect(() => {
console.log("Row " + position + " state changed:");
console.log(rowState);
}, [rowState])
return (
<table className="ingredient-widget"><tbody>
<tr>
<td className="quantity-of-unit">
<TextField variant="outlined" label="Quantity" />
<TextField variant="outlined" label="Quantity" onChange={(e) => setRowState({...rowState, quantity: parseFloat(e.target.value) })} />
</td>
<td className="ingredient-unit">
<Autocomplete
@@ -34,22 +61,16 @@ function IngredientSelector({ position, ingredients, units, destroy }: Ingredien
label="Unit"
/>
)}
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 });
}}
/>
</td>
<td className="ingredient-name">
<Autocomplete
autoHighlight
options={options}
options={ingredientOptions}
freeSolo
className="ui-creatable-component"
renderInput={(params) => (
<TextField
@@ -58,15 +79,36 @@ function IngredientSelector({ position, ingredients, units, destroy }: Ingredien
label="Ingredient Name"
/>
)}
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
}
})
}
}}
/>
</td>
<td>

View File

@@ -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<IRecipe>({ name: '', preptime: '', description: '', authoruserid: '' })
// UI state handling
const [ingredients, setIngredients] = useState<IIngredient[]>([]);
const [measurements, setMeasurements] = useState<DropdownData[]>([]);
const [ingredientFields, setIngredientFields] = useState<Array<JSX.Element>>([]);
const [courseData, setCourseData] = useState<DropdownData[]>([]);
const [ingredientFields, setIngredientFields] = useState<Array<JSX.Element>>([]);
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([<IngredientSelector key={v4()} position={optionCount} ingredients={data} units={measurements} destroy={destroySelector} />]);
if (ingredients.length && measurements.length) {
setIngredientFields([<IngredientSelector key={v4()} position={optionCount} ingredients={ingredients} units={measurements} destroy={destroySelector} />]);
}
}, [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, <IngredientSelector position={optionCount + 1} key={v4()} ingredients={data} units={measurements} destroy={destroySelector} />])
setIngredientFields((prev) => [...prev, <IngredientSelector position={optionCount + 1} key={v4()} ingredients={ingredients} units={measurements} destroy={destroySelector} />])
setOptionCount(prev => prev + 1);
}
@@ -144,7 +144,7 @@ const AddRecipe = () => {
/>}
</div>
{ data && (
{ ingredients && (
<>
<Card extraClasses="form-row flex-row ingredient-card">
<label id="ingredients-label">Ingredients:</label>

View File

@@ -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<IRecipe>();
const { user, token } = useAuthContext();
const { id } = useParams();
if (!id) {
return (
<Page>
<h1>404 | Not Found</h1>
<p>There's no content here! Technically, you shouldn't have even been able to get here.</p>
<p>So, kudos, I guess!</p>
</Page>
)
}
const [recipe, setRecipe] = useState<IRecipe | "no recipe">();
const [userData, setUserData] = useState<IUser>();
const [ingredientData, setIngredientData] = useState<IIngredient[]>([]);
const [view, setView] = useState<JSX.Element>(<h1>Loading...</h1>);
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 (
<Protect redirect={`/recipe/${id}`}>
{ recipe && (
<Panel>
useEffect(() => {
if (recipe) {
if (recipe === "no recipe") {
setView(<ResourceNotFound><h2>We couldn't find a recipe with the ID {id}.</h2></ResourceNotFound>);
} 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(
<Protect redirect={`/recipe/${id}`}>
<h1>{recipe.name}</h1>
<p>{recipe.description}</p>
<p>{recipe.preptime}</p>
</Panel>
)}
</Protect>
)
<h2>Provided courtesy of {userData.firstname} {userData.lastname}</h2>
<Divider />
<p>Prep time: {recipe.preptime}</p>
<Divider />
<h2>Ingredients:</h2>
{ ingredientData.length
? ingredientData.map((each: IIngredient) => <p>{each.name}</p>)
: "No ingredients for this recipe"
}
<Divider />
<div dangerouslySetInnerHTML={{ __html: (recipe.description || "")}}></div>
</Protect>
);
}
}, [userData, ingredientData]);
return view
}

View File

@@ -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<ICollection> {