reworking view recipe by id
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
return (
|
const result = await recipeAPI.getByID(id);
|
||||||
<Protect redirect={`/recipe/${id}`}>
|
if (result) {
|
||||||
{ recipe && (
|
setRecipe(result);
|
||||||
<Panel>
|
} else {
|
||||||
<h1>{recipe.name}</h1>
|
setRecipe("no recipe");
|
||||||
<p>{recipe.description}</p>
|
}
|
||||||
<p>{recipe.preptime}</p>
|
})()
|
||||||
</Panel>
|
}
|
||||||
)}
|
}, [token])
|
||||||
</Protect>
|
|
||||||
)
|
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>
|
||||||
|
<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
|
||||||
}
|
}
|
||||||
@@ -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> {
|
||||||
|
|||||||
Reference in New Issue
Block a user