in progress: associate ingredients to recipe on save
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { Autocomplete, TextField } from "@mui/material"
|
import { Autocomplete, TextField } from "@mui/material"
|
||||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||||
import { useAuthContext } from "../../context/AuthContext";
|
import { useAuthContext } from "../../context/AuthContext";
|
||||||
import { DropdownData, IIngredient } from "../../schemas";
|
import { DropdownData, IIngredient, RecipeIngredient } from "../../schemas";
|
||||||
import { IngredientFieldData } from "../../util/types";
|
import { IngredientFieldData } from "../../util/types";
|
||||||
import { Button } from "../ui";
|
import { Button } from "../ui";
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ const createIngredient = (name: string, userid: string | number) => {
|
|||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
createdbyid: userid
|
createdbyid: userid
|
||||||
} as IIngredient
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const quantityOptions: readonly any[] = [
|
const quantityOptions: readonly any[] = [
|
||||||
@@ -149,7 +149,15 @@ function IngredientSelector({ position, ingredients, units, getRowState, destroy
|
|||||||
|
|
||||||
if (typeof newValue == 'string') {
|
if (typeof newValue == 'string') {
|
||||||
const newIngredient = createIngredient(newValue, user.id!);
|
const newIngredient = createIngredient(newValue, user.id!);
|
||||||
setIngredientOptions((prev) => [...prev, newValue]);
|
|
||||||
|
setIngredientOptions((prev) => {
|
||||||
|
let shouldInsert = true;
|
||||||
|
for (let each of prev) {
|
||||||
|
if (each == newValue) shouldInsert = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (shouldInsert ? [...prev, newValue] : prev);
|
||||||
|
});
|
||||||
|
|
||||||
setRowState((prev) => {
|
setRowState((prev) => {
|
||||||
let shouldInsert = true;
|
let shouldInsert = true;
|
||||||
|
|||||||
@@ -130,9 +130,17 @@ export default function AddRecipe() {
|
|||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
if (!user || !token) return;
|
if (!user || !token) return;
|
||||||
|
|
||||||
|
// initialize API handlers
|
||||||
|
const recipeAPI = new API.Recipe(token);
|
||||||
|
const ingredientAPI = new API.Ingredient(token);
|
||||||
|
|
||||||
|
// array to aggregate error/success messages
|
||||||
|
let messages = new Array<string>();
|
||||||
|
|
||||||
// inject current user id into recipe entry
|
// inject current user id into recipe entry
|
||||||
setInput({ ...input, authoruserid: user.id! });
|
setInput({ ...input, authoruserid: user.id! });
|
||||||
|
|
||||||
|
// verify all required fields are set
|
||||||
for (let field of Object.keys(input)) {
|
for (let field of Object.keys(input)) {
|
||||||
// account for an edge case where this state may not have been set yet
|
// account for an edge case where this state may not have been set yet
|
||||||
if (field == 'authoruserid' as keyof IRecipe) {
|
if (field == 'authoruserid' as keyof IRecipe) {
|
||||||
@@ -140,16 +148,57 @@ export default function AddRecipe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!input[field as keyof IRecipe]) {
|
if (!input[field as keyof IRecipe]) {
|
||||||
|
messages.push("Missing required field " + field);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipe = new API.Recipe(token);
|
let ingredientSelections = new Array<any>();
|
||||||
const result = await recipe.post(input);
|
let newIngredientCount = 0;
|
||||||
|
|
||||||
|
// handle ingredient row data
|
||||||
|
for (let row of ingredientFieldData) {
|
||||||
|
if (row.ingredientSelection === undefined) {
|
||||||
|
messages.push("Please ensure you have included ingredient selections for all rows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ingredientSelections.push(row.ingredientSelection);
|
||||||
|
|
||||||
|
for (let ing of row.ingredients) {
|
||||||
|
// filter out recipes that already exist
|
||||||
|
if (ingredients.filter(x => x.name == ing.name).includes(ing)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the ingredient list
|
||||||
|
setIngredients((prev) => [...prev, ing]);
|
||||||
|
|
||||||
|
// post the new ingredient to the database
|
||||||
|
const newEntry = await ingredientAPI.post(ing);
|
||||||
|
messages.push(`Successfully created new ingredient: ${ing.name}!`);
|
||||||
|
console.log(newEntry);
|
||||||
|
newIngredientCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// post recipe entry
|
||||||
|
const result = await recipeAPI.post(input);
|
||||||
|
|
||||||
|
// handle recipe post resolve/reject
|
||||||
if (result) {
|
if (result) {
|
||||||
const recipeID = result.recipe.id;
|
const recipeID = result.recipe.id;
|
||||||
const recipeName = result.recipe.name;
|
const recipeName = result.recipe.name;
|
||||||
|
let recipeIngredientCount = 0;
|
||||||
|
|
||||||
|
for (let ing of ingredientSelections) {
|
||||||
|
const ok = await recipeAPI.addIngredientToRecipe(ing, recipeID);
|
||||||
|
if (ok) recipeIngredientCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.push(`Created recipe ${recipeName} with ${recipeIngredientCount} total ingredients!`)
|
||||||
|
if (newIngredientCount > 0) {
|
||||||
|
messages.push(`Successfully created ${newIngredientCount} new ingredients! Thanks for helping us grow.`);
|
||||||
|
}
|
||||||
|
|
||||||
setToast(
|
setToast(
|
||||||
<Toast>
|
<Toast>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AxiosError, AxiosHeaders, AxiosRequestHeaders, AxiosResponse } from "axios";
|
import { AxiosError, AxiosHeaders, AxiosRequestHeaders, AxiosResponse } from "axios";
|
||||||
import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroceryList, DropdownData } from "../schemas";
|
import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroceryList, DropdownData, RecipeIngredient } from "../schemas";
|
||||||
import { default as _instance } from "./axiosInstance";
|
import { default as _instance } from "./axiosInstance";
|
||||||
|
|
||||||
module API {
|
module API {
|
||||||
@@ -187,6 +187,11 @@ module API {
|
|||||||
constructor(token: string) {
|
constructor(token: string) {
|
||||||
super(Settings.getAPISTRING() + "/app/recipe", token);
|
super(Settings.getAPISTRING() + "/app/recipe", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addIngredientToRecipe(ingredient: RecipeIngredient, recipeid: string | number) {
|
||||||
|
const response = await this.instance.post(this.endpoint + `?addIngredients=true&recipeID=${recipeid}`, JSON.stringify(ingredient), this.headers);
|
||||||
|
return Promise.resolve(response.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Ingredient extends RestController<IIngredient> {
|
export class Ingredient extends RestController<IIngredient> {
|
||||||
@@ -194,11 +199,6 @@ module API {
|
|||||||
super(Settings.getAPISTRING() + "/app/ingredient", token);
|
super(Settings.getAPISTRING() + "/app/ingredient", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
async associateIngredientWithRecipe(recipeID: string | number, ingredientID: string | number) {
|
|
||||||
const response = await this.instance.post(this.endpoint + `/${ingredientID}?recipeID=${recipeID}`, this.headers);
|
|
||||||
return Promise.resolve(response.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllForRecipe(recipeID: string | number) {
|
async getAllForRecipe(recipeID: string | number) {
|
||||||
const response = await this.instance.get(this.endpoint + `?recipeID=${recipeID}`, this.headers);
|
const response = await this.instance.get(this.endpoint + `?recipeID=${recipeID}`, this.headers);
|
||||||
return Promise.resolve(response.data);
|
return Promise.resolve(response.data);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IRecipe } from "../schemas";
|
import { IRecipe, RecipeIngredient } from "../schemas";
|
||||||
import { Recipe } from "../models/recipe";
|
import { Recipe } from "../models/recipe";
|
||||||
import ControllerResponse from "../util/ControllerResponse";
|
import ControllerResponse from "../util/ControllerResponse";
|
||||||
import { StatusCode } from "../util/types";
|
import { StatusCode } from "../util/types";
|
||||||
@@ -58,4 +58,15 @@ export default class RecipeCtl {
|
|||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addIngredientToRecipe(ingredient: RecipeIngredient, recipeid: string | number) {
|
||||||
|
try {
|
||||||
|
const result = await RecipeInstance.addIngredientToRecipe(ingredient, recipeid);
|
||||||
|
const ok = result !== null;
|
||||||
|
const code = ok ? StatusCode.NewContent : StatusCode.BadRequest;
|
||||||
|
return new ControllerResponse(code, (result || "Something went wrong"));
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IRecipe } from "../schemas";
|
import { IIngredient, IRecipe, RecipeIngredient } from "../schemas";
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import pool from "../db";
|
import pool from "../db";
|
||||||
import { CollectionCtl } from "../controllers";
|
import { CollectionCtl } from "../controllers";
|
||||||
@@ -114,4 +114,24 @@ export class Recipe {
|
|||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addIngredientToRecipe(ingredient: RecipeIngredient, recipeid: string | number) {
|
||||||
|
const { quantity, unit, id } = ingredient;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const statement = `
|
||||||
|
INSERT INTO recipin.cmp_recipeingredient
|
||||||
|
(quantity, unit, ingredientid, recipeid)
|
||||||
|
VALUES ($1, $2, $3, $4) RETURNING *
|
||||||
|
`
|
||||||
|
|
||||||
|
const result = await pool.query(statement, [quantity, unit, id, recipeid]);
|
||||||
|
|
||||||
|
if (result.rows) return result.rows[0];
|
||||||
|
return [];
|
||||||
|
} catch (e: any) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -51,4 +51,6 @@ export const ingredientRoute = (app: Express) => {
|
|||||||
next(e);
|
next(e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return router;
|
||||||
}
|
}
|
||||||
@@ -57,10 +57,16 @@ export const recipeRoute = (app: Express) => {
|
|||||||
router.post('/', restrictAccess, async (req, res, next) => {
|
router.post('/', restrictAccess, async (req, res, next) => {
|
||||||
const user = req.user as IUser;
|
const user = req.user as IUser;
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
const { addIngredients, recipeID } = req.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await recipectl.post(user.id as number, data);
|
if (addIngredients) {
|
||||||
res.status(result.code).send(result.data);
|
const result = await recipectl.addIngredientToRecipe(data, recipeID as string);
|
||||||
|
res.status(result.code).send(result.data);
|
||||||
|
} else {
|
||||||
|
const result = await recipectl.post(user.id as number, data);
|
||||||
|
res.status(result.code).send(result.data);
|
||||||
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
next(e);
|
next(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ export interface IIngredient extends HasHistory {
|
|||||||
createdbyid: string | number
|
createdbyid: string | number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RecipeIngredient extends Partial<IIngredient> {
|
||||||
|
unit: string
|
||||||
|
quantity: string | number
|
||||||
|
}
|
||||||
|
|
||||||
export interface ICollection extends HasHistory, CanDeactivate {
|
export interface ICollection extends HasHistory, CanDeactivate {
|
||||||
name: string
|
name: string
|
||||||
ismaincollection: boolean
|
ismaincollection: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user