mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	fix: consoldate stores to fix mismatched state
This commit is contained in:
		| @@ -118,7 +118,7 @@ | ||||
|  | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api"; | ||||
| import { useFoods, useUnits } from "~/composables/recipes"; | ||||
| import { useFoodStore, useFoodData, useUnitStore, useUnitData } from "~/composables/store"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| import { RecipeIngredient } from "~/types/api-types/recipe"; | ||||
|  | ||||
| @@ -136,24 +136,28 @@ export default defineComponent({ | ||||
|   setup(props) { | ||||
|     // ================================================== | ||||
|     // Foods | ||||
|     const { foods, workingFoodData, actions: foodActions } = useFoods(); | ||||
|     const foodStore = useFoodStore(); | ||||
|     const foodData = useFoodData(); | ||||
|     const foodSearch = ref(""); | ||||
|  | ||||
|     async function createAssignFood() { | ||||
|       workingFoodData.name = foodSearch.value; | ||||
|       await foodActions.createOne(); | ||||
|       props.value.food = foods.value?.find((food) => food.name === foodSearch.value); | ||||
|       foodData.data.name = foodSearch.value; | ||||
|       await foodStore.actions.createOne(foodData.data); | ||||
|       props.value.food = foodStore.foods.value?.find((food) => food.name === foodSearch.value); | ||||
|       foodData.reset(); | ||||
|     } | ||||
|  | ||||
|     // ================================================== | ||||
|     // Units | ||||
|     const { units, workingUnitData, actions: unitActions } = useUnits(); | ||||
|     const unitStore = useUnitStore(); | ||||
|     const unitsData = useUnitData(); | ||||
|     const unitSearch = ref(""); | ||||
|  | ||||
|     async function createAssignUnit() { | ||||
|       workingUnitData.name = unitSearch.value; | ||||
|       await unitActions.createOne(); | ||||
|       props.value.unit = units.value?.find((unit) => unit.name === unitSearch.value); | ||||
|       unitsData.data.name = unitSearch.value; | ||||
|       await unitStore.actions.createOne(unitsData.data); | ||||
|       props.value.unit = unitStore.units.value?.find((unit) => unit.name === unitSearch.value); | ||||
|       unitsData.reset(); | ||||
|     } | ||||
|  | ||||
|     const state = reactive({ | ||||
| @@ -226,22 +230,22 @@ export default defineComponent({ | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|       ...toRefs(state), | ||||
|       quantityFilter, | ||||
|       toggleOriginalText, | ||||
|       contextMenuOptions, | ||||
|       handleUnitEnter, | ||||
|       handleFoodEnter, | ||||
|       ...toRefs(state), | ||||
|       createAssignFood, | ||||
|       createAssignUnit, | ||||
|       foods, | ||||
|       foods: foodStore.foods, | ||||
|       foodSearch, | ||||
|       toggleTitle, | ||||
|       unitActions, | ||||
|       units, | ||||
|       unitActions: unitStore.actions, | ||||
|       units: unitStore.units, | ||||
|       unitSearch, | ||||
|       validators, | ||||
|       workingUnitData, | ||||
|       workingUnitData: unitsData.data, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
|   | ||||
| @@ -19,16 +19,12 @@ | ||||
|       <div class="ingredient-grid"> | ||||
|         <div class="ingredient-col-1"> | ||||
|           <ul> | ||||
|             <li v-for="(text, index) in splitIngredients.firstHalf" :key="index"> | ||||
|               {{ text }} | ||||
|             </li> | ||||
|             <li v-for="(text, index) in splitIngredients.firstHalf" :key="index" v-html="text" /> | ||||
|           </ul> | ||||
|         </div> | ||||
|         <div class="ingredient-col-2"> | ||||
|           <ul> | ||||
|             <li v-for="(text, index) in splitIngredients.secondHalf" :key="index"> | ||||
|               {{ text }} | ||||
|             </li> | ||||
|             <li v-for="(text, index) in splitIngredients.secondHalf" :key="index" v-html="text" /> | ||||
|           </ul> | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
							
								
								
									
										90
									
								
								frontend/composables/partials/use-actions-factory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								frontend/composables/partials/use-actions-factory.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| import { Ref, useAsync } from "@nuxtjs/composition-api"; | ||||
| import { useAsyncKey } from "../use-utils"; | ||||
| import { BaseCRUDAPI } from "~/api/_base"; | ||||
|  | ||||
| type BoundT = { | ||||
|   id: string | number; | ||||
| }; | ||||
|  | ||||
| interface StoreActions<T extends BoundT> { | ||||
|   getAll(): Ref<T[] | null>; | ||||
|   refresh(): Promise<void>; | ||||
|   createOne(createData: T): Promise<void>; | ||||
|   updateOne(updateData: T): Promise<void>; | ||||
|   deleteOne(id: string | number): Promise<void>; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * useStoreActions is a factory function that returns a set of methods | ||||
|  * that can be reused to manage the state of a data store without using | ||||
|  * Vuex. This is primarily used for basic CRUD operations that required | ||||
|  * a lot of refreshing hooks to be called on operations | ||||
|  */ | ||||
| export function useStoreActions<T extends BoundT>( | ||||
|   api: BaseCRUDAPI<unknown, T, unknown>, | ||||
|   allRef: Ref<T[] | null> | null, | ||||
|   loading: Ref<boolean> | ||||
| ): StoreActions<T> { | ||||
|   function getAll() { | ||||
|     loading.value = true; | ||||
|     const allItems = useAsync(async () => { | ||||
|       const { data } = await api.getAll(); | ||||
|       return data; | ||||
|     }, useAsyncKey()); | ||||
|  | ||||
|     loading.value = false; | ||||
|     return allItems; | ||||
|   } | ||||
|  | ||||
|   async function refresh() { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.getAll(); | ||||
|  | ||||
|     if (data && allRef) { | ||||
|       allRef.value = data; | ||||
|     } | ||||
|  | ||||
|     loading.value = false; | ||||
|   } | ||||
|  | ||||
|   async function createOne(createData: T) { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.createOne(createData); | ||||
|     if (data && allRef?.value) { | ||||
|       allRef.value.push(data); | ||||
|     } else { | ||||
|       refresh(); | ||||
|     } | ||||
|     loading.value = false; | ||||
|   } | ||||
|  | ||||
|   async function updateOne(updateData: T) { | ||||
|     if (!updateData.id) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     loading.value = true; | ||||
|     const { data } = await api.updateOne(updateData.id, updateData); | ||||
|     if (data && allRef?.value) { | ||||
|       refresh(); | ||||
|     } | ||||
|     loading.value = false; | ||||
|   } | ||||
|  | ||||
|   async function deleteOne(id: string | number) { | ||||
|     loading.value = true; | ||||
|     const { data } = await api.deleteOne(id); | ||||
|     if (data && allRef?.value) { | ||||
|       refresh(); | ||||
|     } | ||||
|     loading.value = false; | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     getAll, | ||||
|     refresh, | ||||
|     createOne, | ||||
|     updateOne, | ||||
|     deleteOne, | ||||
|   }; | ||||
| } | ||||
| @@ -1,7 +1,5 @@ | ||||
| export { useFraction } from "./use-fraction"; | ||||
| export { useRecipe } from "./use-recipe"; | ||||
| export { useFoods } from "./use-recipe-foods"; | ||||
| export { useUnits } from "./use-recipe-units"; | ||||
| export { useRecipes, recentRecipes, allRecipes, useLazyRecipes, useSorter } from "./use-recipes"; | ||||
| export { useTags, useCategories, allCategories, allTags } from "./use-tags-categories"; | ||||
| export { parseIngredientText } from "./use-recipe-ingredients"; | ||||
|   | ||||
| @@ -1,104 +0,0 @@ | ||||
| import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api"; | ||||
| import { useAsyncKey } from "../use-utils"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { VForm } from "~/types/vuetify"; | ||||
| import { IngredientFood } from "~/types/api-types/recipe"; | ||||
|  | ||||
| let foodStore: Ref<IngredientFood[] | null> | null = null; | ||||
|  | ||||
| export const useFoods = function () { | ||||
|   const api = useUserApi(); | ||||
|   const loading = ref(false); | ||||
|   const deleteTargetId = ref(0); | ||||
|   const validForm = ref(true); | ||||
|  | ||||
|   const workingFoodData = reactive<IngredientFood>({ | ||||
|     id: "", | ||||
|     name: "", | ||||
|     description: "", | ||||
|     labelId: undefined, | ||||
|   }); | ||||
|  | ||||
|   const actions = { | ||||
|     getAll() { | ||||
|       loading.value = true; | ||||
|       const units = useAsync(async () => { | ||||
|         const { data } = await api.foods.getAll(); | ||||
|         return data; | ||||
|       }, useAsyncKey()); | ||||
|  | ||||
|       loading.value = false; | ||||
|       return units; | ||||
|     }, | ||||
|     async refreshAll() { | ||||
|       loading.value = true; | ||||
|       const { data } = await api.foods.getAll(); | ||||
|  | ||||
|       if (data && foodStore) { | ||||
|         foodStore.value = data; | ||||
|       } | ||||
|  | ||||
|       loading.value = false; | ||||
|     }, | ||||
|     async createOne(domForm: VForm | null = null) { | ||||
|       if (domForm && !domForm.validate()) { | ||||
|         validForm.value = false; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       loading.value = true; | ||||
|       const { data } = await api.foods.createOne(workingFoodData); | ||||
|       if (data && foodStore?.value) { | ||||
|         foodStore.value.push(data); | ||||
|         return data; | ||||
|       } else { | ||||
|         this.refreshAll(); | ||||
|       } | ||||
|       domForm?.reset(); | ||||
|       validForm.value = true; | ||||
|       this.resetWorking(); | ||||
|       loading.value = false; | ||||
|     }, | ||||
|     async updateOne() { | ||||
|       if (!workingFoodData.id) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       loading.value = true; | ||||
|       console.log(workingFoodData); | ||||
|       const { data } = await api.foods.updateOne(workingFoodData.id, workingFoodData); | ||||
|       if (data && foodStore?.value) { | ||||
|         this.refreshAll(); | ||||
|       } | ||||
|       loading.value = false; | ||||
|     }, | ||||
|     async deleteOne(id: string | number) { | ||||
|       loading.value = true; | ||||
|       const { data } = await api.foods.deleteOne(id); | ||||
|       if (data && foodStore?.value) { | ||||
|         this.refreshAll(); | ||||
|       } | ||||
|     }, | ||||
|     resetWorking() { | ||||
|       workingFoodData.id = ""; | ||||
|       workingFoodData.name = ""; | ||||
|       workingFoodData.description = ""; | ||||
|       workingFoodData.labelId = undefined; | ||||
|     }, | ||||
|     setWorking(item: IngredientFood) { | ||||
|       workingFoodData.id = item.id; | ||||
|       workingFoodData.name = item.name; | ||||
|       workingFoodData.description = item.description || ""; | ||||
|       workingFoodData.labelId = item.labelId; | ||||
|     }, | ||||
|     flushStore() { | ||||
|       foodStore = null; | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   if (!foodStore) { | ||||
|     foodStore = actions.getAll(); | ||||
|   } | ||||
|  | ||||
|   return { foods: foodStore, workingFoodData, deleteTargetId, actions, validForm }; | ||||
| }; | ||||
| @@ -1,104 +0,0 @@ | ||||
| import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api"; | ||||
| import { useAsyncKey } from "../use-utils"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { VForm } from "~/types/vuetify"; | ||||
| import { IngredientUnit } from "~/types/api-types/recipe"; | ||||
|  | ||||
| let unitStore: Ref<IngredientUnit[] | null> | null = null; | ||||
|  | ||||
| export const useUnits = function () { | ||||
|   const api = useUserApi(); | ||||
|   const loading = ref(false); | ||||
|   const deleteTargetId = ref(0); | ||||
|   const validForm = ref(true); | ||||
|  | ||||
|   const workingUnitData: IngredientUnit = reactive({ | ||||
|     id: "", | ||||
|     name: "", | ||||
|     fraction: true, | ||||
|     abbreviation: "", | ||||
|     description: "", | ||||
|   }); | ||||
|  | ||||
|   const actions = { | ||||
|     getAll() { | ||||
|       loading.value = true; | ||||
|       const units = useAsync(async () => { | ||||
|         const { data } = await api.units.getAll(); | ||||
|         return data; | ||||
|       }, useAsyncKey()); | ||||
|  | ||||
|       loading.value = false; | ||||
|       return units; | ||||
|     }, | ||||
|     async refreshAll() { | ||||
|       loading.value = true; | ||||
|       const { data } = await api.units.getAll(); | ||||
|  | ||||
|       if (data && unitStore) { | ||||
|         unitStore.value = data; | ||||
|       } | ||||
|  | ||||
|       loading.value = false; | ||||
|     }, | ||||
|     async createOne(domForm: VForm | null = null) { | ||||
|       if (domForm && !domForm.validate()) { | ||||
|         validForm.value = false; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       loading.value = true; | ||||
|       const { data } = await api.units.createOne(workingUnitData); | ||||
|       if (data && unitStore?.value) { | ||||
|         unitStore.value.push(data); | ||||
|       } else { | ||||
|         this.refreshAll(); | ||||
|       } | ||||
|       domForm?.reset(); | ||||
|       validForm.value = true; | ||||
|       this.resetWorking(); | ||||
|       loading.value = false; | ||||
|     }, | ||||
|     async updateOne() { | ||||
|       if (!workingUnitData.id) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       loading.value = true; | ||||
|       const { data } = await api.units.updateOne(workingUnitData.id, workingUnitData); | ||||
|       if (data && unitStore?.value) { | ||||
|         this.refreshAll(); | ||||
|       } | ||||
|       loading.value = false; | ||||
|     }, | ||||
|     async deleteOne(id: string | number) { | ||||
|       loading.value = true; | ||||
|       const { data } = await api.units.deleteOne(id); | ||||
|       if (data && unitStore?.value) { | ||||
|         this.refreshAll(); | ||||
|       } | ||||
|     }, | ||||
|     resetWorking() { | ||||
|       workingUnitData.id = ""; | ||||
|       workingUnitData.name = ""; | ||||
|       workingUnitData.abbreviation = ""; | ||||
|       workingUnitData.description = ""; | ||||
|     }, | ||||
|     setWorking(item: IngredientUnit) { | ||||
|       workingUnitData.id = item.id; | ||||
|       workingUnitData.name = item.name; | ||||
|       workingUnitData.fraction = item.fraction; | ||||
|       workingUnitData.abbreviation = item.abbreviation; | ||||
|       workingUnitData.description = item.description; | ||||
|     }, | ||||
|     flushStore() { | ||||
|       unitStore = null; | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   if (!unitStore) { | ||||
|     unitStore = actions.getAll(); | ||||
|   } | ||||
|  | ||||
|   return { units: unitStore, workingUnitData, deleteTargetId, actions, validForm }; | ||||
| }; | ||||
							
								
								
									
										3
									
								
								frontend/composables/store/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								frontend/composables/store/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| export { useFoodStore, useFoodData } from "./use-food-store"; | ||||
| export { useUnitStore, useUnitData } from "./use-unit-store"; | ||||
| export { useLabelStore, useLabelData } from "./use-label-store"; | ||||
							
								
								
									
										50
									
								
								frontend/composables/store/use-food-store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								frontend/composables/store/use-food-store.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import { ref, reactive, Ref } from "@nuxtjs/composition-api"; | ||||
| import { useStoreActions } from "../partials/use-actions-factory"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { IngredientFood } from "~/types/api-types/recipe"; | ||||
|  | ||||
| let foodStore: Ref<IngredientFood[] | null> | null = null; | ||||
|  | ||||
| /** | ||||
|  * useFoodData returns a template reactive object | ||||
|  * for managing the creation of units. It also provides a | ||||
|  * function to reset the data back to the initial state. | ||||
|  */ | ||||
| export const useFoodData = function () { | ||||
|   const data: IngredientFood = reactive({ | ||||
|     id: "", | ||||
|     name: "", | ||||
|     description: "", | ||||
|     labelId: undefined, | ||||
|   }); | ||||
|  | ||||
|   function reset() { | ||||
|     data.id = ""; | ||||
|     data.name = ""; | ||||
|     data.description = ""; | ||||
|     data.labelId = undefined; | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     data, | ||||
|     reset, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const useFoodStore = function () { | ||||
|   const api = useUserApi(); | ||||
|   const loading = ref(false); | ||||
|  | ||||
|   const actions = { | ||||
|     ...useStoreActions(api.foods, foodStore, loading), | ||||
|     flushStore() { | ||||
|       foodStore = null; | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   if (!foodStore) { | ||||
|     foodStore = actions.getAll(); | ||||
|   } | ||||
|  | ||||
|   return { foods: foodStore, actions }; | ||||
| }; | ||||
							
								
								
									
										49
									
								
								frontend/composables/store/use-label-store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								frontend/composables/store/use-label-store.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| import { reactive, ref, Ref } from "@nuxtjs/composition-api"; | ||||
| import { useStoreActions } from "../partials/use-actions-factory"; | ||||
| import { MultiPurposeLabelOut } from "~/types/api-types/labels"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
|  | ||||
| let labelStore: Ref<MultiPurposeLabelOut[] | null> | null = null; | ||||
|  | ||||
| export function useLabelData() { | ||||
|   const data = reactive({ | ||||
|     groupId: "", | ||||
|     id: "", | ||||
|     name: "", | ||||
|     color: "", | ||||
|   }); | ||||
|  | ||||
|   function reset() { | ||||
|     data.groupId = ""; | ||||
|     data.id = ""; | ||||
|     data.name = ""; | ||||
|     data.color = ""; | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     data, | ||||
|     reset, | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export function useLabelStore() { | ||||
|   const api = useUserApi(); | ||||
|   const loading = ref(false); | ||||
|  | ||||
|   const actions = { | ||||
|     ...useStoreActions<MultiPurposeLabelOut>(api.multiPurposeLabels, labelStore, loading), | ||||
|     flushStore() { | ||||
|       labelStore = null; | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   if (!labelStore) { | ||||
|     labelStore = actions.getAll(); | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     labels: labelStore, | ||||
|     actions, | ||||
|     loading, | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										52
									
								
								frontend/composables/store/use-unit-store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/composables/store/use-unit-store.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| import { ref, reactive, Ref } from "@nuxtjs/composition-api"; | ||||
| import { useStoreActions } from "../partials/use-actions-factory"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { IngredientUnit } from "~/types/api-types/recipe"; | ||||
|  | ||||
| let unitStore: Ref<IngredientUnit[] | null> | null = null; | ||||
|  | ||||
| /** | ||||
|  * useUnitData returns a template reactive object | ||||
|  * for managing the creation of units. It also provides a | ||||
|  * function to reset the data back to the initial state. | ||||
|  */ | ||||
| export const useUnitData = function () { | ||||
|   const data: IngredientUnit = reactive({ | ||||
|     id: "", | ||||
|     name: "", | ||||
|     fraction: true, | ||||
|     abbreviation: "", | ||||
|     description: "", | ||||
|   }); | ||||
|  | ||||
|   function reset() { | ||||
|     data.id = ""; | ||||
|     data.name = ""; | ||||
|     data.fraction = true; | ||||
|     data.abbreviation = ""; | ||||
|     data.description = ""; | ||||
|   } | ||||
|  | ||||
|   return { | ||||
|     data, | ||||
|     reset, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const useUnitStore = function () { | ||||
|   const api = useUserApi(); | ||||
|   const loading = ref(false); | ||||
|  | ||||
|   const actions = { | ||||
|     ...useStoreActions<IngredientUnit>(api.units, unitStore, loading), | ||||
|     flushStore() { | ||||
|       unitStore = null; | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   if (!unitStore) { | ||||
|     unitStore = actions.getAll(); | ||||
|   } | ||||
|  | ||||
|   return { units: unitStore, actions }; | ||||
| }; | ||||
| @@ -48,7 +48,7 @@ | ||||
|           </template> | ||||
|         </v-autocomplete> | ||||
|  | ||||
|         <v-alert v-if="foods.length > 0" type="error" class="mb-0 text-body-2"> | ||||
|         <v-alert v-if="foods && foods.length > 0" type="error" class="mb-0 text-body-2"> | ||||
|           {{ $t("data-pages.foods.seed-dialog-warning") }} | ||||
|         </v-alert> | ||||
|       </v-card-text> | ||||
| @@ -96,7 +96,7 @@ | ||||
|     <CrudTable | ||||
|       :table-config="tableConfig" | ||||
|       :headers.sync="tableHeaders" | ||||
|       :data="foods" | ||||
|       :data="foods || []" | ||||
|       :bulk-actions="[]" | ||||
|       @delete-one="deleteEventHandler" | ||||
|       @edit-one="editEventHandler" | ||||
| @@ -132,6 +132,7 @@ import { IngredientFood } from "~/types/api-types/recipe"; | ||||
| import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue"; | ||||
| import { MultiPurposeLabelSummary } from "~/types/api-types/labels"; | ||||
| import { useLocales } from "~/composables/use-locales"; | ||||
| import { useFoodStore, useLabelStore } from "~/composables/store"; | ||||
|  | ||||
| export default defineComponent({ | ||||
|   components: { MultiPurposeLabel }, | ||||
| @@ -163,32 +164,32 @@ export default defineComponent({ | ||||
|         show: true, | ||||
|       }, | ||||
|     ]; | ||||
|     const foods = ref<IngredientFood[]>([]); | ||||
|     async function refreshFoods() { | ||||
|       const { data } = await userApi.foods.getAll(); | ||||
|       foods.value = data ?? []; | ||||
|     } | ||||
|     onMounted(() => { | ||||
|       refreshFoods(); | ||||
|     }); | ||||
|  | ||||
|     const foodStore = useFoodStore(); | ||||
|  | ||||
|     // =============================================================== | ||||
|     // Food Editor | ||||
|  | ||||
|     const editDialog = ref(false); | ||||
|     const editTarget = ref<IngredientFood | null>(null); | ||||
|  | ||||
|     function editEventHandler(item: IngredientFood) { | ||||
|       editTarget.value = item; | ||||
|       editDialog.value = true; | ||||
|     } | ||||
|  | ||||
|     async function editSaveFood() { | ||||
|       if (!editTarget.value) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       const { data } = await userApi.foods.updateOne(editTarget.value.id, editTarget.value); | ||||
|       if (data) { | ||||
|         refreshFoods(); | ||||
|       } | ||||
|  | ||||
|       await foodStore.actions.updateOne(editTarget.value); | ||||
|       editDialog.value = false; | ||||
|     } | ||||
|  | ||||
|     // =============================================================== | ||||
|     // Food Delete | ||||
|  | ||||
|     const deleteDialog = ref(false); | ||||
|     const deleteTarget = ref<IngredientFood | null>(null); | ||||
|     function deleteEventHandler(item: IngredientFood) { | ||||
| @@ -200,10 +201,7 @@ export default defineComponent({ | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       const { data } = await userApi.foods.deleteOne(deleteTarget.value.id); | ||||
|       if (data) { | ||||
|         refreshFoods(); | ||||
|       } | ||||
|       await foodStore.actions.deleteOne(deleteTarget.value.id); | ||||
|       deleteDialog.value = false; | ||||
|     } | ||||
|  | ||||
| @@ -226,19 +224,14 @@ export default defineComponent({ | ||||
|       const { data } = await userApi.foods.merge(fromFood.value.id, toFood.value.id); | ||||
|  | ||||
|       if (data) { | ||||
|         refreshFoods(); | ||||
|         foodStore.actions.refresh(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // ============================================================ | ||||
|     // Labels | ||||
|  | ||||
|     const allLabels = ref([] as MultiPurposeLabelSummary[]); | ||||
|  | ||||
|     async function refreshLabels() { | ||||
|       const { data } = await userApi.multiPurposeLabels.getAll(); | ||||
|       allLabels.value = data ?? []; | ||||
|     } | ||||
|     const { labels: allLabels } = useLabelStore(); | ||||
|  | ||||
|     // ============================================================ | ||||
|     // Seed | ||||
| @@ -260,15 +253,14 @@ export default defineComponent({ | ||||
|       const { data } = await userApi.seeders.foods({ locale: locale.value }); | ||||
|  | ||||
|       if (data) { | ||||
|         refreshFoods(); | ||||
|         foodStore.actions.refresh(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     refreshLabels(); | ||||
|     return { | ||||
|       tableConfig, | ||||
|       tableHeaders, | ||||
|       foods, | ||||
|       foods: foodStore.foods, | ||||
|       allLabels, | ||||
|       validators, | ||||
|       // Edit | ||||
|   | ||||
| @@ -73,7 +73,7 @@ | ||||
|           </template> | ||||
|         </v-autocomplete> | ||||
|  | ||||
|         <v-alert v-if="labels.length > 0" type="error" class="mb-0 text-body-2"> | ||||
|         <v-alert v-if="labels && labels.length > 0" type="error" class="mb-0 text-body-2"> | ||||
|           {{ $t("data-pages.foods.seed-dialog-warning") }} | ||||
|         </v-alert> | ||||
|       </v-card-text> | ||||
| @@ -84,7 +84,7 @@ | ||||
|     <CrudTable | ||||
|       :table-config="tableConfig" | ||||
|       :headers.sync="tableHeaders" | ||||
|       :data="labels" | ||||
|       :data="labels || []" | ||||
|       :bulk-actions="[]" | ||||
|       @delete-one="deleteEventHandler" | ||||
|       @edit-one="editEventHandler" | ||||
| @@ -118,6 +118,7 @@ import { useUserApi } from "~/composables/api"; | ||||
| import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue"; | ||||
| import { MultiPurposeLabelSummary } from "~/types/api-types/labels"; | ||||
| import { useLocales } from "~/composables/use-locales"; | ||||
| import { useLabelData, useLabelStore } from "~/composables/store"; | ||||
|  | ||||
| export default defineComponent({ | ||||
|   components: { MultiPurposeLabel }, | ||||
| @@ -149,31 +150,14 @@ export default defineComponent({ | ||||
|     // ============================================================ | ||||
|     // Labels | ||||
|  | ||||
|     const labels = ref([] as MultiPurposeLabelSummary[]); | ||||
|  | ||||
|     async function refreshLabels() { | ||||
|       const { data } = await userApi.multiPurposeLabels.getAll(); | ||||
|       labels.value = data ?? []; | ||||
|     } | ||||
|     const labelData = useLabelData(); | ||||
|     const labelStore = useLabelStore(); | ||||
|  | ||||
|     // Create | ||||
|  | ||||
|     const createLabelData = ref({ | ||||
|       groupId: "", | ||||
|       id: "", | ||||
|       name: "", | ||||
|       color: "", | ||||
|     }); | ||||
|  | ||||
|     async function createLabel() { | ||||
|       await userApi.multiPurposeLabels.createOne(createLabelData.value); | ||||
|       createLabelData.value = { | ||||
|         groupId: "", | ||||
|         id: "", | ||||
|         name: "", | ||||
|         color: "", | ||||
|       }; | ||||
|       refreshLabels(); | ||||
|       await labelStore.actions.createOne(labelData.data); | ||||
|       labelData.reset(); | ||||
|       state.createDialog = false; | ||||
|     } | ||||
|  | ||||
| @@ -190,10 +174,7 @@ export default defineComponent({ | ||||
|       if (!deleteTarget.value) { | ||||
|         return; | ||||
|       } | ||||
|       const { data } = await userApi.multiPurposeLabels.deleteOne(deleteTarget.value.id); | ||||
|       if (data) { | ||||
|         refreshLabels(); | ||||
|       } | ||||
|       await labelStore.actions.deleteOne(deleteTarget.value.id); | ||||
|       state.deleteDialog = false; | ||||
|     } | ||||
|  | ||||
| @@ -214,15 +195,10 @@ export default defineComponent({ | ||||
|       if (!editLabel.value) { | ||||
|         return; | ||||
|       } | ||||
|       const { data } = await userApi.multiPurposeLabels.updateOne(editLabel.value.id, editLabel.value); | ||||
|       if (data) { | ||||
|         refreshLabels(); | ||||
|       } | ||||
|       await labelStore.actions.updateOne(editLabel.value); | ||||
|       state.editDialog = false; | ||||
|     } | ||||
|  | ||||
|     refreshLabels(); | ||||
|  | ||||
|     // ============================================================ | ||||
|     // Seed | ||||
|  | ||||
| @@ -243,7 +219,7 @@ export default defineComponent({ | ||||
|       const { data } = await userApi.seeders.labels({ locale: locale.value }); | ||||
|  | ||||
|       if (data) { | ||||
|         refreshLabels(); | ||||
|         labelStore.actions.refresh(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -251,7 +227,7 @@ export default defineComponent({ | ||||
|       state, | ||||
|       tableConfig, | ||||
|       tableHeaders, | ||||
|       labels, | ||||
|       labels: labelStore.labels, | ||||
|       validators, | ||||
|  | ||||
|       deleteEventHandler, | ||||
| @@ -260,7 +236,7 @@ export default defineComponent({ | ||||
|       editEventHandler, | ||||
|       editSaveLabel, | ||||
|       createLabel, | ||||
|       createLabelData, | ||||
|       createLabelData: labelData.data, | ||||
|  | ||||
|       // Seed | ||||
|       seedDatabase, | ||||
|   | ||||
| @@ -6,8 +6,15 @@ | ||||
|         Combining the selected units will merge the Source Unit and Target Unit into a single unit. The | ||||
|         <strong> Source Unit will be deleted </strong> and all of the references to the Source Unit will be updated to | ||||
|         point to the Target Unit. | ||||
|         <v-autocomplete v-model="fromUnit" return-object :items="units" item-text="name" label="Source Unit" /> | ||||
|         <v-autocomplete v-model="toUnit" return-object :items="units" item-text="name" label="Target Unit" /> | ||||
|  | ||||
|         <v-autocomplete v-model="fromUnit" return-object :items="units" item-text="id" label="Source Unit"> | ||||
|           <template #selection="{ item }"> {{ item.name }}</template> | ||||
|           <template #item="{ item }"> {{ item.name }} </template> | ||||
|         </v-autocomplete> | ||||
|         <v-autocomplete v-model="toUnit" return-object :items="units" item-text="id" label="Target Unit"> | ||||
|           <template #selection="{ item }"> {{ item.name }}</template> | ||||
|           <template #item="{ item }"> {{ item.name }} </template> | ||||
|         </v-autocomplete> | ||||
|  | ||||
|         <template v-if="canMerge && fromUnit && toUnit"> | ||||
|           <div class="text-center">Merging {{ fromUnit.name }} into {{ toUnit.name }}</div> | ||||
| @@ -77,7 +84,7 @@ | ||||
|           </template> | ||||
|         </v-autocomplete> | ||||
|  | ||||
|         <v-alert v-if="units.length > 0" type="error" class="mb-0 text-body-2"> | ||||
|         <v-alert v-if="units && units.length > 0" type="error" class="mb-0 text-body-2"> | ||||
|           {{ $t("data-pages.foods.seed-dialog-warning") }} | ||||
|         </v-alert> | ||||
|       </v-card-text> | ||||
| @@ -88,7 +95,7 @@ | ||||
|     <CrudTable | ||||
|       :table-config="tableConfig" | ||||
|       :headers.sync="tableHeaders" | ||||
|       :data="units" | ||||
|       :data="units || []" | ||||
|       :bulk-actions="[]" | ||||
|       @delete-one="deleteEventHandler" | ||||
|       @edit-one="editEventHandler" | ||||
| @@ -120,8 +127,8 @@ import type { LocaleObject } from "@nuxtjs/i18n"; | ||||
| import { validators } from "~/composables/use-validators"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { IngredientUnit } from "~/types/api-types/recipe"; | ||||
| import { MultiPurposeLabelSummary } from "~/types/api-types/labels"; | ||||
| import { useLocales } from "~/composables/use-locales"; | ||||
| import { useUnitStore } from "~/composables/store"; | ||||
|  | ||||
| export default defineComponent({ | ||||
|   setup() { | ||||
| @@ -157,47 +164,39 @@ export default defineComponent({ | ||||
|         show: true, | ||||
|       }, | ||||
|     ]; | ||||
|     const units = ref<IngredientUnit[]>([]); | ||||
|     async function refreshUnits() { | ||||
|       const { data } = await userApi.units.getAll(); | ||||
|       units.value = data ?? []; | ||||
|     } | ||||
|     onMounted(() => { | ||||
|       refreshUnits(); | ||||
|     }); | ||||
|  | ||||
|     const { units, actions: unitActions } = useUnitStore(); | ||||
|  | ||||
|     // Edit Units | ||||
|     const editDialog = ref(false); | ||||
|     const editTarget = ref<IngredientUnit | null>(null); | ||||
|     function editEventHandler(item: IngredientUnit) { | ||||
|       editTarget.value = item; | ||||
|       editDialog.value = true; | ||||
|     } | ||||
|  | ||||
|     async function editSaveUnit() { | ||||
|       if (!editTarget.value) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       const { data } = await userApi.units.updateOne(editTarget.value.id, editTarget.value); | ||||
|       if (data) { | ||||
|         refreshUnits(); | ||||
|       } | ||||
|  | ||||
|       await unitActions.updateOne(editTarget.value); | ||||
|       editDialog.value = false; | ||||
|     } | ||||
|  | ||||
|     // Delete Units | ||||
|     const deleteDialog = ref(false); | ||||
|     const deleteTarget = ref<IngredientUnit | null>(null); | ||||
|     function deleteEventHandler(item: IngredientUnit) { | ||||
|       deleteTarget.value = item; | ||||
|       deleteDialog.value = true; | ||||
|     } | ||||
|  | ||||
|     async function deleteUnit() { | ||||
|       if (!deleteTarget.value) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       const { data } = await userApi.units.deleteOne(deleteTarget.value.id); | ||||
|       if (data) { | ||||
|         refreshUnits(); | ||||
|       } | ||||
|       await unitActions.deleteOne(deleteTarget.value.id); | ||||
|       deleteDialog.value = false; | ||||
|     } | ||||
|  | ||||
| @@ -220,22 +219,10 @@ export default defineComponent({ | ||||
|       const { data } = await userApi.units.merge(fromUnit.value.id, toUnit.value.id); | ||||
|  | ||||
|       if (data) { | ||||
|         refreshUnits(); | ||||
|         unitActions.refresh(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // ============================================================ | ||||
|     // Labels | ||||
|  | ||||
|     const allLabels = ref([] as MultiPurposeLabelSummary[]); | ||||
|  | ||||
|     async function refreshLabels() { | ||||
|       const { data } = await userApi.multiPurposeLabels.getAll(); | ||||
|       allLabels.value = data ?? []; | ||||
|     } | ||||
|  | ||||
|     refreshLabels(); | ||||
|  | ||||
|     // ============================================================ | ||||
|     // Seed | ||||
|  | ||||
| @@ -256,7 +243,7 @@ export default defineComponent({ | ||||
|       const { data } = await userApi.seeders.units({ locale: locale.value }); | ||||
|  | ||||
|       if (data) { | ||||
|         refreshUnits(); | ||||
|         unitActions.refresh(); | ||||
|       } | ||||
|     } | ||||
|  | ||||
| @@ -264,7 +251,6 @@ export default defineComponent({ | ||||
|       tableConfig, | ||||
|       tableHeaders, | ||||
|       units, | ||||
|       allLabels, | ||||
|       validators, | ||||
|       // Edit | ||||
|       editDialog, | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|         <div> | ||||
|           Mealie can use natural language processing to attempt to parse and create units, and foods for your Recipe | ||||
|           ingredients. This is experimental and may not work as expected. If you choose to not use the parsed results | ||||
|           you can seleect cancel and your changes will not be saved. | ||||
|           you can select cancel and your changes will not be saved. | ||||
|         </div> | ||||
|       </v-alert> | ||||
|  | ||||
| @@ -14,7 +14,7 @@ | ||||
|         To use the ingredient parser, click the "Parse All" button and the process will start. When the processed | ||||
|         ingredients are available, you can look through the items and verify that they were parsed correctly. The models | ||||
|         confidence score is displayed on the right of the title item. This is an average of all scores and may not be | ||||
|         wholey accurate. | ||||
|         wholely accurate. | ||||
|  | ||||
|         <div class="my-4"> | ||||
|           Alerts will be displayed if a matching foods or unit is found but does not exists in the database. | ||||
| @@ -84,11 +84,18 @@ | ||||
| import { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api"; | ||||
| import { invoke, until } from "@vueuse/core"; | ||||
| import { Parser } from "~/api/class-interfaces/recipes/recipe"; | ||||
| import { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit, ParsedIngredient } from "~/types/api-types/recipe"; | ||||
| import { | ||||
|   CreateIngredientFood, | ||||
|   CreateIngredientUnit, | ||||
|   IngredientFood, | ||||
|   IngredientUnit, | ||||
|   ParsedIngredient, | ||||
| } from "~/types/api-types/recipe"; | ||||
| import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue"; | ||||
| import { useUserApi } from "~/composables/api"; | ||||
| import { useFoods, useRecipe, useUnits } from "~/composables/recipes"; | ||||
| import { useRecipe } from "~/composables/recipes"; | ||||
| import { RecipeIngredient } from "~/types/api-types/admin"; | ||||
| import { useFoodData, useFoodStore, useUnitStore } from "~/composables/store"; | ||||
|  | ||||
| interface Error { | ||||
|   ingredientIndex: number; | ||||
| @@ -182,8 +189,9 @@ export default defineComponent({ | ||||
|     // ========================================================= | ||||
|     // Food and Ingredient Logic | ||||
|  | ||||
|     const { foods, workingFoodData, actions } = useFoods(); | ||||
|     const { units } = useUnits(); | ||||
|     const foodStore = useFoodStore(); | ||||
|     const foodData = useFoodData(); | ||||
|     const { units } = useUnitStore(); | ||||
|  | ||||
|     const errors = ref<Error[]>([]); | ||||
|  | ||||
| @@ -201,16 +209,17 @@ export default defineComponent({ | ||||
|       if (!food) { | ||||
|         return false; | ||||
|       } | ||||
|       if (foods.value && food?.name) { | ||||
|         return foods.value.some((f) => f.name === food.name); | ||||
|       if (foodStore.foods.value && food?.name) { | ||||
|         return foodStore.foods.value.some((f) => f.name === food.name); | ||||
|       } | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     async function createFood(food: CreateIngredientFood, index: number) { | ||||
|       workingFoodData.name = food.name; | ||||
|       await actions.createOne(); | ||||
|       foodData.data.name = food.name; | ||||
|       await foodStore.actions.createOne(foodData.data); | ||||
|       errors.value[index].foodError = false; | ||||
|       foodData.reset(); | ||||
|     } | ||||
|  | ||||
|     // ========================================================= | ||||
| @@ -219,16 +228,16 @@ export default defineComponent({ | ||||
|       let ingredients = parsedIng.value.map((ing) => { | ||||
|         return { | ||||
|           ...ing.ingredient, | ||||
|           originalText: ing.input | ||||
|           originalText: ing.input, | ||||
|         } as RecipeIngredient; | ||||
|       }); | ||||
|  | ||||
|       ingredients = ingredients.map((ing) => { | ||||
|         if (!foods.value || !units.value) { | ||||
|         if (!foodStore.foods.value || !units.value) { | ||||
|           return ing; | ||||
|         } | ||||
|         // Get food from foods | ||||
|         ing.food = foods.value.find((f) => f.name === ing.food?.name); | ||||
|         ing.food = foodStore.foods.value.find((f) => f.name === ing.food?.name); | ||||
|  | ||||
|         // Get unit from units | ||||
|         ing.unit = units.value.find((u) => u.name === ing.unit?.name); | ||||
| @@ -252,8 +261,8 @@ export default defineComponent({ | ||||
|       saveAll, | ||||
|       createFood, | ||||
|       errors, | ||||
|       actions, | ||||
|       workingFoodData, | ||||
|       actions: foodStore.actions, | ||||
|       workingFoodData: foodData, | ||||
|       isError, | ||||
|       panels, | ||||
|       asPercentage, | ||||
|   | ||||
| @@ -108,10 +108,11 @@ import { defineComponent, toRefs, computed, reactive } from "@nuxtjs/composition | ||||
| import RecipeSearchFilterSelector from "~/components/Domain/Recipe/RecipeSearchFilterSelector.vue"; | ||||
| import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue"; | ||||
| import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue"; | ||||
| import { useRecipes, allRecipes, useFoods } from "~/composables/recipes"; | ||||
| import { useRecipes, allRecipes } from "~/composables/recipes"; | ||||
| import { RecipeSummary } from "~/types/api-types/recipe"; | ||||
| import { useRouteQuery } from "~/composables/use-router"; | ||||
| import { RecipeTag } from "~/types/api-types/user"; | ||||
| import { useFoodStore } from "~/composables/store"; | ||||
|  | ||||
| interface GenericFilter { | ||||
|   exclude: boolean; | ||||
| @@ -259,7 +260,7 @@ export default defineComponent({ | ||||
|       state.foodFilter = params; | ||||
|     } | ||||
|  | ||||
|     const { foods } = useFoods(); | ||||
|     const { foods } = useFoodStore(); | ||||
|  | ||||
|     return { | ||||
|       ...toRefs(state), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user