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 { 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 { DropdownData, IIngredient } from "../../schemas";
import { Button } from "../ui"; import { Button } from "../ui";
@@ -10,17 +11,43 @@ interface IngredientSelectorProps {
destroy: (position: number) => void 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) { 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 [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 ( return (
<table className="ingredient-widget"><tbody> <table className="ingredient-widget"><tbody>
<tr> <tr>
<td className="quantity-of-unit"> <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>
<td className="ingredient-unit"> <td className="ingredient-unit">
<Autocomplete <Autocomplete
@@ -34,22 +61,16 @@ function IngredientSelector({ position, ingredients, units, destroy }: Ingredien
label="Unit" label="Unit"
/> />
)} )}
onKeyDown={(e) => { onChange={(event, value) => {
console.log(e.code); setRowState({ ...rowState, measurement: value });
/* 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]);
}
} */
}} }}
/> />
</td> </td>
<td className="ingredient-name"> <td className="ingredient-name">
<Autocomplete <Autocomplete
autoHighlight autoHighlight
options={options} options={ingredientOptions}
freeSolo
className="ui-creatable-component" className="ui-creatable-component"
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField
@@ -58,15 +79,36 @@ function IngredientSelector({ position, ingredients, units, destroy }: Ingredien
label="Ingredient Name" label="Ingredient Name"
/> />
)} )}
onKeyDown={(e) => { onChange={(event, newValue) => {
if (e.code == 'Enter') { if (!user) return;
const inputVal: string = e.target['value' as keyof EventTarget].toString();
if (inputVal.length) { if (typeof newValue == 'string') {
setSelected(prev => [...prev, inputVal]) const newIngredient = createIngredient(newValue, user.id!);
setOptions((prev) => [...prev, inputVal]); 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>
<td> <td>

View File

@@ -12,15 +12,15 @@ import { Autocomplete, TextField } from "@mui/material";
const AddRecipe = () => { const AddRecipe = () => {
const { user, token } = useAuthContext(); const { user, token } = useAuthContext();
const { data, setData } = useSelectorContext();
// received recipe data // received recipe data
const [input, setInput] = useState<IRecipe>({ name: '', preptime: '', description: '', authoruserid: '' }) const [input, setInput] = useState<IRecipe>({ name: '', preptime: '', description: '', authoruserid: '' })
// UI state handling // UI state handling
const [ingredients, setIngredients] = useState<IIngredient[]>([]);
const [measurements, setMeasurements] = useState<DropdownData[]>([]); const [measurements, setMeasurements] = useState<DropdownData[]>([]);
const [ingredientFields, setIngredientFields] = useState<Array<JSX.Element>>([]);
const [courseData, setCourseData] = useState<DropdownData[]>([]); const [courseData, setCourseData] = useState<DropdownData[]>([]);
const [ingredientFields, setIngredientFields] = useState<Array<JSX.Element>>([]);
const [optionCount, setOptionCount] = useState(0); const [optionCount, setOptionCount] = useState(0);
// status reporting // status reporting
@@ -31,12 +31,12 @@ const AddRecipe = () => {
token && (async() => { token && (async() => {
const ingredients = new API.Ingredient(token); const ingredients = new API.Ingredient(token);
const _dropdowns = new API.Dropdowns(token); const _dropdowns = new API.Dropdowns(token);
const result = await ingredients.getAll(); const ingredientList = await ingredients.getAll();
const measurementList = await _dropdowns.getAllMeasurements(); const measurementList = await _dropdowns.getAllMeasurements();
const courseList = await _dropdowns.getAllCourses(); const courseList = await _dropdowns.getAllCourses();
if (result) { if (ingredientList) {
setData((prev) => [...prev, ...result]); setIngredients((prev) => [...prev, ...ingredientList]);
} }
if (measurementList) { if (measurementList) {
@@ -50,11 +50,11 @@ const AddRecipe = () => {
}, [token]) }, [token])
useEffect(() => { useEffect(() => {
if (data.length && measurements.length) { if (ingredients.length && measurements.length) {
setIngredientFields([<IngredientSelector key={v4()} position={optionCount} ingredients={data} units={measurements} destroy={destroySelector} />]); 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 // once user information is available, store it in recipe data
useEffect(() => { useEffect(() => {
@@ -107,7 +107,7 @@ const AddRecipe = () => {
}, [ingredientFields]); }, [ingredientFields]);
function handleNewOption() { 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); setOptionCount(prev => prev + 1);
} }
@@ -144,7 +144,7 @@ const AddRecipe = () => {
/>} />}
</div> </div>
{ data && ( { ingredients && (
<> <>
<Card extraClasses="form-row flex-row ingredient-card"> <Card extraClasses="form-row flex-row ingredient-card">
<label id="ingredients-label">Ingredients:</label> <label id="ingredients-label">Ingredients:</label>

View File

@@ -1,37 +1,92 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { Page, Panel } from "../ui"; import { Divider, Page, Panel } from "../ui";
import { IRecipe } from "../../schemas"; import { IRecipe, IUser, IIngredient } from "../../schemas";
import { getRecipeByID } from "../../util/apiUtils"; import { getRecipeByID } from "../../util/apiUtils";
import Protect from "../../util/Protect"; import Protect from "../../util/Protect";
import API from "../../util/API";
import { useAuthContext } from "../../context/AuthContext";
import ResourceNotFound from "./StatusPages/404";
export default function Recipe() { export default function Recipe() {
const [recipe, setRecipe] = useState<IRecipe>(); const { user, token } = useAuthContext();
const { id } = useParams(); const { id } = useParams();
if (!id) { const [recipe, setRecipe] = useState<IRecipe | "no recipe">();
return ( const [userData, setUserData] = useState<IUser>();
<Page> const [ingredientData, setIngredientData] = useState<IIngredient[]>([]);
<h1>404 | Not Found</h1> const [view, setView] = useState<JSX.Element>(<h1>Loading...</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>
)
}
useEffect(() => { 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 ( 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}`}> <Protect redirect={`/recipe/${id}`}>
{ recipe && (
<Panel>
<h1>{recipe.name}</h1> <h1>{recipe.name}</h1>
<p>{recipe.description}</p> <h2>Provided courtesy of {userData.firstname} {userData.lastname}</h2>
<p>{recipe.preptime}</p>
</Panel> <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> </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); const response = await this.instance.post(this.endpoint + `/${ingredientID}?recipeID=${recipeID}`, this.headers);
return Promise.resolve(response.data); 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> { export class Collection extends RestController<ICollection> {