Skip to content

Commit

Permalink
Fix form editing
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkjellid committed Jun 19, 2024
1 parent 6c963ed commit b3a834f
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 50 deletions.
102 changes: 85 additions & 17 deletions frontend/apps/recipes/components/RecipeForm/RecipeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,52 @@ function RecipeForm({ recipe, ingredients, onSubmit }: RecipeFormProps) {
},
groupDelete: function (index: number) {
const ingredientGroupsData = [...ingredientGroups]
const ingredientGroup = ingredientGroupsData[index]
ingredientGroupsData.splice(index, 1)

const stepsData = [...steps]
const stepsWithIngredientItemsFromGroup = steps.filter((step) =>
step.ingredientItems.some((ingredientItem) => {
if ('id' in ingredientItem) {
return ingredientGroup.ingredientItems.some((item) => item.id === ingredientItem.id)
} else {
return ingredientGroup.ingredientItems.some(
(item) =>
item.ingredient.id === ingredientItem.ingredient.id &&
item.portionQuantity === ingredientItem.portionQuantity &&
item.portionQuantityUnit.id === ingredientItem.portionQuantityUnit.id &&
item.additionalInfo === ingredientItem.additionalInfo
)
}
})
)

stepsWithIngredientItemsFromGroup.forEach((step) => {
const stepIndex = stepsData.indexOf(step)

if (stepIndex === -1) return

const ingredientsData = [...step.ingredientItems].filter((ingredientItem) => {
if ('id' in ingredientItem) {
return !ingredientGroup.ingredientItems.some((item) => item.id === ingredientItem.id)
} else {
return !ingredientGroup.ingredientItems.some(
(item) =>
item.ingredient.id === ingredientItem.ingredient.id &&
item.portionQuantity === ingredientItem.portionQuantity &&
item.portionQuantityUnit.id === ingredientItem.portionQuantityUnit.id &&
item.additionalInfo === ingredientItem.additionalInfo
)
}
})

step.ingredientItems = ingredientsData

stepsData[stepIndex] = step
})

setIngredientGroups(ingredientGroupsData)
setSteps(stepsData)
resetValidation()
},
groupSequenceChange: function (data: IngredientItemGroup[]) {
Expand All @@ -106,36 +150,60 @@ function RecipeForm({ recipe, ingredients, onSubmit }: RecipeFormProps) {
inputDelete: function (index: number, ingredientIndex: number) {
const ingredientGroupsData = [...ingredientGroups]
const ingredientGroup = ingredientGroupsData[index]
const ingredientItemToDelete = ingredientGroup.ingredientItems[ingredientIndex]
const ingredientsData = [...ingredientGroup.ingredientItems]

const deletedIngredientItems = ingredientsData.splice(ingredientIndex, 1)
ingredientGroup.ingredientItems = ingredientsData

setIngredientGroups(ingredientGroupsData)

// Update related steps as well so that they don't automatically get assigned the ingredient
// if it's added back.
const stepsData = [...steps]
const deletedIngredientIds = deletedIngredientItems.map(
(ingredientItem) => ingredientItem.ingredient.id
)
const stepsWithAssignedIngredientItem = steps.filter((step) =>
step.ingredientItems.filter((ingredientItem) =>
deletedIngredientIds.includes(ingredientItem.ingredient.id)
)
step.ingredientItems.some((ingredientItem) => {
if ('id' in ingredientItem) {
return ingredientGroup.ingredientItems.some((item) => item.id === ingredientItem.id)
} else {
return ingredientGroup.ingredientItems.some(
(item) =>
item.ingredient.id === ingredientItem.ingredient.id &&
item.portionQuantity === ingredientItem.portionQuantity &&
item.portionQuantityUnit.id === ingredientItem.portionQuantityUnit.id &&
item.additionalInfo === ingredientItem.additionalInfo
)
}
})
)

stepsWithAssignedIngredientItem.forEach((step) => {
const stepIndex = stepsData.indexOf(step)

if (stepIndex === -1) return

const ingredientsData = [...step.ingredientItems]
ingredientsData.splice(ingredientIndex, 1)
step.ingredientItems = ingredientsData
const ingredientItemData = [...step.ingredientItems]
const localIngredientItem = ingredientItemData.find((ingredientItem) => {
if ('id' in ingredientItem && 'id' in ingredientItemToDelete) {
return ingredientItem.id === ingredientItemToDelete.id
} else {
return (
ingredientItemToDelete.ingredient.id === ingredientItem.ingredient.id &&
ingredientItemToDelete.portionQuantity === ingredientItem.portionQuantity &&
ingredientItemToDelete.portionQuantityUnit.id ===
ingredientItem.portionQuantityUnit.id &&
ingredientItemToDelete.additionalInfo === ingredientItem.additionalInfo
)
}
})

if (!localIngredientItem) return

const ingredientItemIndex = ingredientItemData.indexOf(localIngredientItem)
if (ingredientItemIndex === -1) return

ingredientItemData.splice(ingredientItemIndex, 1)
step.ingredientItems = ingredientItemData

stepsData[stepIndex] = step
})

ingredientsData.splice(ingredientIndex, 1)
ingredientGroup.ingredientItems = ingredientsData

setIngredientGroups(ingredientGroupsData)
setSteps(stepsData)
resetValidation()
},
Expand Down
49 changes: 32 additions & 17 deletions frontend/apps/recipes/components/RecipeForm/StepInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,25 +101,40 @@ function StepInput({

// Since we need the group index and the ingredient id, back map the ingredient item and find appropriate
// item group.
const getSelectedIngredientGroupItems = useMemo(() => {
const sequenceMapping: string[][] = []
ingredientGroups.flatMap((ingredientGroup, ingredientGroupIndex) =>
ingredientGroup.ingredientItems.map((ingredientItem) => {
const ingredientItemFromStep = step.ingredientItems.find(
(stepIngredientItem) => ingredientItem.ingredient.id === stepIngredientItem.ingredient.id
const selectedIngredientItems = useMemo(() => {
const mappedIngredientItems: string[] = []

step.ingredientItems.flatMap((ingredientItem) => {
let ingredientItemGroup: IngredientItemGroup | undefined = undefined

if ('id' in ingredientItem) {
// If we have an id, the ingredient item already exist in the db, and hence back mapping is easier.
ingredientItemGroup = ingredientGroups.find((ingredientGroup) =>
ingredientGroup.ingredientItems.some((item) => item.id === ingredientItem.id)
)
} else {
// If not, we try to match on all the properties of the ingredient item to hopefully find the correct one.
// There are some pitfalls here if we have to different groups with the same ingredient item with all data
// matching, but it's highly unlikely.
ingredientItemGroup = ingredientGroups.find((ingredientGroup) =>
ingredientGroup.ingredientItems.some(
(item) =>
item.ingredient.id === ingredientItem.ingredient.id &&
item.portionQuantity === ingredientItem.portionQuantity &&
item.portionQuantityUnit.id === ingredientItem.portionQuantityUnit.id &&
item.additionalInfo === ingredientItem.additionalInfo
)
)
}

if (ingredientItemFromStep) {
sequenceMapping.push([
ingredientGroupIndex.toString(),
ingredientItemFromStep?.ingredient.id.toString(),
])
}
})
)
if (ingredientItemGroup) {
const groupIndex = ingredientGroups.indexOf(ingredientItemGroup)
mappedIngredientItems.push(`${groupIndex}-${ingredientItem.ingredient.id}`)
}
})

return sequenceMapping.flatMap(([groupIndex, ingredientId]) => `${groupIndex}-${ingredientId}`)
}, [ingredientGroups, step])
return mappedIngredientItems
}, [ingredientGroups, step.ingredientItems])

const getErrorForField = (field: string) => {
if (!errors) return undefined
Expand Down Expand Up @@ -171,7 +186,7 @@ function StepInput({
<MultiSelect
label="Ingredients"
description="Pick ingredients required in this step"
value={getSelectedIngredientGroupItems}
value={selectedIngredientItems}
data={ingredientOptions}
searchable
clearable
Expand Down
1 change: 1 addition & 0 deletions frontend/apps/recipes/components/RecipeForm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface IngredientItem
RecipeIngredientItemRecord,
'ingredient' | 'portionQuantityUnit' | 'additionalInfo'
> {
id?: number
portionQuantity: string | number // Allow empty string for input
}
export interface IngredientItemGroup
Expand Down
9 changes: 5 additions & 4 deletions frontend/apps/recipes/components/RecipeForm/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ const makeStepType = (step: FormStep | RecipeStepRecord, index: number): Step =>
})

const makeIngredientItemGroupType = (
ingredientGroup: FromIngredientItemGroup | RecipeIngredientItemGroupRecord
ingredientGroup: FromIngredientItemGroup | RecipeIngredientItemGroupRecord,
index: number
): IngredientGroupItem => ({
id: 'id' in ingredientGroup ? ingredientGroup.id : undefined,
title: ingredientGroup.title,
ordering: ingredientGroup.ordering,
ordering: index + 1,
ingredientItems: ingredientGroup.ingredientItems.map((ingredientItem) =>
makeIngredientItemType(ingredientItem)
),
Expand All @@ -52,8 +53,8 @@ const makePayload = (recipeData: Recipe): RecipeCreateIn | RecipeEditIn => ({
baseRecipe: { ...recipeData.baseRecipe },
steps: [...recipeData.steps.map((step, index) => makeStepType(step, index))],
ingredientItemGroups: [
...recipeData.ingredientItemGroups.map((ingredientGroup) =>
makeIngredientItemGroupType(ingredientGroup)
...recipeData.ingredientItemGroups.map((ingredientGroup, index) =>
makeIngredientItemGroupType(ingredientGroup, index)
),
],
})
Expand Down
3 changes: 1 addition & 2 deletions frontend/hooks/forms/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,13 @@ export function useForm<T extends object>({
}

if (validator.errors) {
console.error(validator.errors)
validator.errors.map((error) => {
const pathParts = error.instancePath.split('/')
const inputKey = pathParts[pathParts.length - 1]
const errorMsg = error.message

if (!inputKey || !errorMsg || !schema.required.includes(inputKey)) return

console.error(error)
// @ts-ignore
errors[inputKey] = errorMsg.charAt(0).toUpperCase() + errorMsg.slice(1).toLocaleLowerCase()
})
Expand Down
15 changes: 15 additions & 0 deletions nest/recipes/ingredients/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,15 @@ def create_or_update_recipe_ingredient_item_groups(

_validate_ingredient_item_groups(ingredient_group_items=ingredient_item_groups)

existing_groups = list(
RecipeIngredientItemGroup.objects.filter(recipe_id=recipe_id)
)

groups_to_create: list[RecipeIngredientItemGroup] = []
groups_to_update: list[RecipeIngredientItemGroup] = []

# Delete stale groups

for item_group in ingredient_item_groups:
item_group_id = getattr(item_group, "id", None)
correct_list = groups_to_update if item_group_id else groups_to_create
Expand All @@ -202,6 +208,15 @@ def create_or_update_recipe_ingredient_item_groups(
fields=["title", "ordering"],
)

group_ids_to_delete = [
group.id
for group in existing_groups
if group.id not in [item_group.id for item_group in groups_to_update]
]

if len(group_ids_to_delete):
RecipeIngredientItemGroup.objects.filter(id__in=group_ids_to_delete).delete()

transaction.on_commit(
functools.partial(
create_or_update_recipe_ingredient_items,
Expand Down
18 changes: 8 additions & 10 deletions nest/recipes/steps/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import functools
from datetime import timedelta
from decimal import Decimal

import structlog
from pydantic import BaseModel
Expand Down Expand Up @@ -92,15 +93,13 @@ def _find_ingredient_item_id_for_step_item(
if item.id is not None:
return item.id

item_id = (
next(
i.id
for i in recipe_ingredient_items
if i.ingredient_id == item.ingredient_id
and i.portion_quantity == item.portion_quantity
and i.portion_quantity_unit_id == item.portion_quantity_unit_id
and i.additional_info == item.additional_info
),
item_id = next(
i.id
for i in recipe_ingredient_items
if i.ingredient_id == int(item.ingredient_id)
and i.portion_quantity == Decimal(item.portion_quantity)
and i.portion_quantity_unit_id == int(item.portion_quantity_unit_id)
and i.additional_info == item.additional_info
)

return item_id
Expand Down Expand Up @@ -130,7 +129,6 @@ def create_or_update_recipe_step_ingredient_items(recipe_id: int, steps: list[St
RecipeStepIngredientItem.objects.filter(step__recipe_id=recipe_id)
)

updated_steps: list[int] = []
steps_to_ignore: list[int] = []
relations_to_create: list[RecipeStepIngredientItem] = []

Expand Down

0 comments on commit b3a834f

Please sign in to comment.