fix: consoldate stores to fix mismatched state

This commit is contained in:
Hayden 2022-05-29 17:29:59 -08:00
parent f831791db2
commit d2a9f7ca24
15 changed files with 348 additions and 350 deletions

View File

@ -118,7 +118,7 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, reactive, ref, toRefs } from "@nuxtjs/composition-api"; 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 { validators } from "~/composables/use-validators";
import { RecipeIngredient } from "~/types/api-types/recipe"; import { RecipeIngredient } from "~/types/api-types/recipe";
@ -136,24 +136,28 @@ export default defineComponent({
setup(props) { setup(props) {
// ================================================== // ==================================================
// Foods // Foods
const { foods, workingFoodData, actions: foodActions } = useFoods(); const foodStore = useFoodStore();
const foodData = useFoodData();
const foodSearch = ref(""); const foodSearch = ref("");
async function createAssignFood() { async function createAssignFood() {
workingFoodData.name = foodSearch.value; foodData.data.name = foodSearch.value;
await foodActions.createOne(); await foodStore.actions.createOne(foodData.data);
props.value.food = foods.value?.find((food) => food.name === foodSearch.value); props.value.food = foodStore.foods.value?.find((food) => food.name === foodSearch.value);
foodData.reset();
} }
// ================================================== // ==================================================
// Units // Units
const { units, workingUnitData, actions: unitActions } = useUnits(); const unitStore = useUnitStore();
const unitsData = useUnitData();
const unitSearch = ref(""); const unitSearch = ref("");
async function createAssignUnit() { async function createAssignUnit() {
workingUnitData.name = unitSearch.value; unitsData.data.name = unitSearch.value;
await unitActions.createOne(); await unitStore.actions.createOne(unitsData.data);
props.value.unit = units.value?.find((unit) => unit.name === unitSearch.value); props.value.unit = unitStore.units.value?.find((unit) => unit.name === unitSearch.value);
unitsData.reset();
} }
const state = reactive({ const state = reactive({
@ -226,22 +230,22 @@ export default defineComponent({
} }
return { return {
...toRefs(state),
quantityFilter, quantityFilter,
toggleOriginalText, toggleOriginalText,
contextMenuOptions, contextMenuOptions,
handleUnitEnter, handleUnitEnter,
handleFoodEnter, handleFoodEnter,
...toRefs(state),
createAssignFood, createAssignFood,
createAssignUnit, createAssignUnit,
foods, foods: foodStore.foods,
foodSearch, foodSearch,
toggleTitle, toggleTitle,
unitActions, unitActions: unitStore.actions,
units, units: unitStore.units,
unitSearch, unitSearch,
validators, validators,
workingUnitData, workingUnitData: unitsData.data,
}; };
}, },
}); });

View File

@ -19,16 +19,12 @@
<div class="ingredient-grid"> <div class="ingredient-grid">
<div class="ingredient-col-1"> <div class="ingredient-col-1">
<ul> <ul>
<li v-for="(text, index) in splitIngredients.firstHalf" :key="index"> <li v-for="(text, index) in splitIngredients.firstHalf" :key="index" v-html="text" />
{{ text }}
</li>
</ul> </ul>
</div> </div>
<div class="ingredient-col-2"> <div class="ingredient-col-2">
<ul> <ul>
<li v-for="(text, index) in splitIngredients.secondHalf" :key="index"> <li v-for="(text, index) in splitIngredients.secondHalf" :key="index" v-html="text" />
{{ text }}
</li>
</ul> </ul>
</div> </div>
</div> </div>

View 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,
};
}

View File

@ -1,7 +1,5 @@
export { useFraction } from "./use-fraction"; export { useFraction } from "./use-fraction";
export { useRecipe } from "./use-recipe"; 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 { useRecipes, recentRecipes, allRecipes, useLazyRecipes, useSorter } from "./use-recipes";
export { useTags, useCategories, allCategories, allTags } from "./use-tags-categories"; export { useTags, useCategories, allCategories, allTags } from "./use-tags-categories";
export { parseIngredientText } from "./use-recipe-ingredients"; export { parseIngredientText } from "./use-recipe-ingredients";

View File

@ -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 };
};

View File

@ -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 };
};

View 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";

View 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 };
};

View 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,
};
}

View 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 };
};

View File

@ -48,7 +48,7 @@
</template> </template>
</v-autocomplete> </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") }} {{ $t("data-pages.foods.seed-dialog-warning") }}
</v-alert> </v-alert>
</v-card-text> </v-card-text>
@ -96,7 +96,7 @@
<CrudTable <CrudTable
:table-config="tableConfig" :table-config="tableConfig"
:headers.sync="tableHeaders" :headers.sync="tableHeaders"
:data="foods" :data="foods || []"
:bulk-actions="[]" :bulk-actions="[]"
@delete-one="deleteEventHandler" @delete-one="deleteEventHandler"
@edit-one="editEventHandler" @edit-one="editEventHandler"
@ -132,6 +132,7 @@ import { IngredientFood } from "~/types/api-types/recipe";
import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue"; import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue";
import { MultiPurposeLabelSummary } from "~/types/api-types/labels"; import { MultiPurposeLabelSummary } from "~/types/api-types/labels";
import { useLocales } from "~/composables/use-locales"; import { useLocales } from "~/composables/use-locales";
import { useFoodStore, useLabelStore } from "~/composables/store";
export default defineComponent({ export default defineComponent({
components: { MultiPurposeLabel }, components: { MultiPurposeLabel },
@ -163,32 +164,32 @@ export default defineComponent({
show: true, show: true,
}, },
]; ];
const foods = ref<IngredientFood[]>([]);
async function refreshFoods() { const foodStore = useFoodStore();
const { data } = await userApi.foods.getAll();
foods.value = data ?? []; // ===============================================================
} // Food Editor
onMounted(() => {
refreshFoods();
});
const editDialog = ref(false); const editDialog = ref(false);
const editTarget = ref<IngredientFood | null>(null); const editTarget = ref<IngredientFood | null>(null);
function editEventHandler(item: IngredientFood) { function editEventHandler(item: IngredientFood) {
editTarget.value = item; editTarget.value = item;
editDialog.value = true; editDialog.value = true;
} }
async function editSaveFood() { async function editSaveFood() {
if (!editTarget.value) { if (!editTarget.value) {
return; return;
} }
const { data } = await userApi.foods.updateOne(editTarget.value.id, editTarget.value); await foodStore.actions.updateOne(editTarget.value);
if (data) {
refreshFoods();
}
editDialog.value = false; editDialog.value = false;
} }
// ===============================================================
// Food Delete
const deleteDialog = ref(false); const deleteDialog = ref(false);
const deleteTarget = ref<IngredientFood | null>(null); const deleteTarget = ref<IngredientFood | null>(null);
function deleteEventHandler(item: IngredientFood) { function deleteEventHandler(item: IngredientFood) {
@ -200,10 +201,7 @@ export default defineComponent({
return; return;
} }
const { data } = await userApi.foods.deleteOne(deleteTarget.value.id); await foodStore.actions.deleteOne(deleteTarget.value.id);
if (data) {
refreshFoods();
}
deleteDialog.value = false; deleteDialog.value = false;
} }
@ -226,19 +224,14 @@ export default defineComponent({
const { data } = await userApi.foods.merge(fromFood.value.id, toFood.value.id); const { data } = await userApi.foods.merge(fromFood.value.id, toFood.value.id);
if (data) { if (data) {
refreshFoods(); foodStore.actions.refresh();
} }
} }
// ============================================================ // ============================================================
// Labels // Labels
const allLabels = ref([] as MultiPurposeLabelSummary[]); const { labels: allLabels } = useLabelStore();
async function refreshLabels() {
const { data } = await userApi.multiPurposeLabels.getAll();
allLabels.value = data ?? [];
}
// ============================================================ // ============================================================
// Seed // Seed
@ -260,15 +253,14 @@ export default defineComponent({
const { data } = await userApi.seeders.foods({ locale: locale.value }); const { data } = await userApi.seeders.foods({ locale: locale.value });
if (data) { if (data) {
refreshFoods(); foodStore.actions.refresh();
} }
} }
refreshLabels();
return { return {
tableConfig, tableConfig,
tableHeaders, tableHeaders,
foods, foods: foodStore.foods,
allLabels, allLabels,
validators, validators,
// Edit // Edit

View File

@ -73,7 +73,7 @@
</template> </template>
</v-autocomplete> </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") }} {{ $t("data-pages.foods.seed-dialog-warning") }}
</v-alert> </v-alert>
</v-card-text> </v-card-text>
@ -84,7 +84,7 @@
<CrudTable <CrudTable
:table-config="tableConfig" :table-config="tableConfig"
:headers.sync="tableHeaders" :headers.sync="tableHeaders"
:data="labels" :data="labels || []"
:bulk-actions="[]" :bulk-actions="[]"
@delete-one="deleteEventHandler" @delete-one="deleteEventHandler"
@edit-one="editEventHandler" @edit-one="editEventHandler"
@ -118,6 +118,7 @@ import { useUserApi } from "~/composables/api";
import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue"; import MultiPurposeLabel from "~/components/Domain/ShoppingList/MultiPurposeLabel.vue";
import { MultiPurposeLabelSummary } from "~/types/api-types/labels"; import { MultiPurposeLabelSummary } from "~/types/api-types/labels";
import { useLocales } from "~/composables/use-locales"; import { useLocales } from "~/composables/use-locales";
import { useLabelData, useLabelStore } from "~/composables/store";
export default defineComponent({ export default defineComponent({
components: { MultiPurposeLabel }, components: { MultiPurposeLabel },
@ -149,31 +150,14 @@ export default defineComponent({
// ============================================================ // ============================================================
// Labels // Labels
const labels = ref([] as MultiPurposeLabelSummary[]); const labelData = useLabelData();
const labelStore = useLabelStore();
async function refreshLabels() {
const { data } = await userApi.multiPurposeLabels.getAll();
labels.value = data ?? [];
}
// Create // Create
const createLabelData = ref({
groupId: "",
id: "",
name: "",
color: "",
});
async function createLabel() { async function createLabel() {
await userApi.multiPurposeLabels.createOne(createLabelData.value); await labelStore.actions.createOne(labelData.data);
createLabelData.value = { labelData.reset();
groupId: "",
id: "",
name: "",
color: "",
};
refreshLabels();
state.createDialog = false; state.createDialog = false;
} }
@ -190,10 +174,7 @@ export default defineComponent({
if (!deleteTarget.value) { if (!deleteTarget.value) {
return; return;
} }
const { data } = await userApi.multiPurposeLabels.deleteOne(deleteTarget.value.id); await labelStore.actions.deleteOne(deleteTarget.value.id);
if (data) {
refreshLabels();
}
state.deleteDialog = false; state.deleteDialog = false;
} }
@ -214,15 +195,10 @@ export default defineComponent({
if (!editLabel.value) { if (!editLabel.value) {
return; return;
} }
const { data } = await userApi.multiPurposeLabels.updateOne(editLabel.value.id, editLabel.value); await labelStore.actions.updateOne(editLabel.value);
if (data) {
refreshLabels();
}
state.editDialog = false; state.editDialog = false;
} }
refreshLabels();
// ============================================================ // ============================================================
// Seed // Seed
@ -243,7 +219,7 @@ export default defineComponent({
const { data } = await userApi.seeders.labels({ locale: locale.value }); const { data } = await userApi.seeders.labels({ locale: locale.value });
if (data) { if (data) {
refreshLabels(); labelStore.actions.refresh();
} }
} }
@ -251,7 +227,7 @@ export default defineComponent({
state, state,
tableConfig, tableConfig,
tableHeaders, tableHeaders,
labels, labels: labelStore.labels,
validators, validators,
deleteEventHandler, deleteEventHandler,
@ -260,7 +236,7 @@ export default defineComponent({
editEventHandler, editEventHandler,
editSaveLabel, editSaveLabel,
createLabel, createLabel,
createLabelData, createLabelData: labelData.data,
// Seed // Seed
seedDatabase, seedDatabase,

View File

@ -6,8 +6,15 @@
Combining the selected units will merge the Source Unit and Target Unit into a single unit. The 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 <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. 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"> <template v-if="canMerge && fromUnit && toUnit">
<div class="text-center">Merging {{ fromUnit.name }} into {{ toUnit.name }}</div> <div class="text-center">Merging {{ fromUnit.name }} into {{ toUnit.name }}</div>
@ -77,7 +84,7 @@
</template> </template>
</v-autocomplete> </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") }} {{ $t("data-pages.foods.seed-dialog-warning") }}
</v-alert> </v-alert>
</v-card-text> </v-card-text>
@ -88,7 +95,7 @@
<CrudTable <CrudTable
:table-config="tableConfig" :table-config="tableConfig"
:headers.sync="tableHeaders" :headers.sync="tableHeaders"
:data="units" :data="units || []"
:bulk-actions="[]" :bulk-actions="[]"
@delete-one="deleteEventHandler" @delete-one="deleteEventHandler"
@edit-one="editEventHandler" @edit-one="editEventHandler"
@ -120,8 +127,8 @@ import type { LocaleObject } from "@nuxtjs/i18n";
import { validators } from "~/composables/use-validators"; import { validators } from "~/composables/use-validators";
import { useUserApi } from "~/composables/api"; import { useUserApi } from "~/composables/api";
import { IngredientUnit } from "~/types/api-types/recipe"; import { IngredientUnit } from "~/types/api-types/recipe";
import { MultiPurposeLabelSummary } from "~/types/api-types/labels";
import { useLocales } from "~/composables/use-locales"; import { useLocales } from "~/composables/use-locales";
import { useUnitStore } from "~/composables/store";
export default defineComponent({ export default defineComponent({
setup() { setup() {
@ -157,47 +164,39 @@ export default defineComponent({
show: true, show: true,
}, },
]; ];
const units = ref<IngredientUnit[]>([]);
async function refreshUnits() { const { units, actions: unitActions } = useUnitStore();
const { data } = await userApi.units.getAll();
units.value = data ?? []; // Edit Units
}
onMounted(() => {
refreshUnits();
});
const editDialog = ref(false); const editDialog = ref(false);
const editTarget = ref<IngredientUnit | null>(null); const editTarget = ref<IngredientUnit | null>(null);
function editEventHandler(item: IngredientUnit) { function editEventHandler(item: IngredientUnit) {
editTarget.value = item; editTarget.value = item;
editDialog.value = true; editDialog.value = true;
} }
async function editSaveUnit() { async function editSaveUnit() {
if (!editTarget.value) { if (!editTarget.value) {
return; return;
} }
const { data } = await userApi.units.updateOne(editTarget.value.id, editTarget.value); await unitActions.updateOne(editTarget.value);
if (data) {
refreshUnits();
}
editDialog.value = false; editDialog.value = false;
} }
// Delete Units
const deleteDialog = ref(false); const deleteDialog = ref(false);
const deleteTarget = ref<IngredientUnit | null>(null); const deleteTarget = ref<IngredientUnit | null>(null);
function deleteEventHandler(item: IngredientUnit) { function deleteEventHandler(item: IngredientUnit) {
deleteTarget.value = item; deleteTarget.value = item;
deleteDialog.value = true; deleteDialog.value = true;
} }
async function deleteUnit() { async function deleteUnit() {
if (!deleteTarget.value) { if (!deleteTarget.value) {
return; return;
} }
await unitActions.deleteOne(deleteTarget.value.id);
const { data } = await userApi.units.deleteOne(deleteTarget.value.id);
if (data) {
refreshUnits();
}
deleteDialog.value = false; deleteDialog.value = false;
} }
@ -220,22 +219,10 @@ export default defineComponent({
const { data } = await userApi.units.merge(fromUnit.value.id, toUnit.value.id); const { data } = await userApi.units.merge(fromUnit.value.id, toUnit.value.id);
if (data) { 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 // Seed
@ -256,7 +243,7 @@ export default defineComponent({
const { data } = await userApi.seeders.units({ locale: locale.value }); const { data } = await userApi.seeders.units({ locale: locale.value });
if (data) { if (data) {
refreshUnits(); unitActions.refresh();
} }
} }
@ -264,7 +251,6 @@ export default defineComponent({
tableConfig, tableConfig,
tableHeaders, tableHeaders,
units, units,
allLabels,
validators, validators,
// Edit // Edit
editDialog, editDialog,

View File

@ -6,7 +6,7 @@
<div> <div>
Mealie can use natural language processing to attempt to parse and create units, and foods for your Recipe 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 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> </div>
</v-alert> </v-alert>
@ -14,7 +14,7 @@
To use the ingredient parser, click the "Parse All" button and the process will start. When the processed 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 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 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"> <div class="my-4">
Alerts will be displayed if a matching foods or unit is found but does not exists in the database. 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 { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api";
import { invoke, until } from "@vueuse/core"; import { invoke, until } from "@vueuse/core";
import { Parser } from "~/api/class-interfaces/recipes/recipe"; 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 RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
import { useUserApi } from "~/composables/api"; 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 { RecipeIngredient } from "~/types/api-types/admin";
import { useFoodData, useFoodStore, useUnitStore } from "~/composables/store";
interface Error { interface Error {
ingredientIndex: number; ingredientIndex: number;
@ -182,8 +189,9 @@ export default defineComponent({
// ========================================================= // =========================================================
// Food and Ingredient Logic // Food and Ingredient Logic
const { foods, workingFoodData, actions } = useFoods(); const foodStore = useFoodStore();
const { units } = useUnits(); const foodData = useFoodData();
const { units } = useUnitStore();
const errors = ref<Error[]>([]); const errors = ref<Error[]>([]);
@ -201,16 +209,17 @@ export default defineComponent({
if (!food) { if (!food) {
return false; return false;
} }
if (foods.value && food?.name) { if (foodStore.foods.value && food?.name) {
return foods.value.some((f) => f.name === food.name); return foodStore.foods.value.some((f) => f.name === food.name);
} }
return false; return false;
} }
async function createFood(food: CreateIngredientFood, index: number) { async function createFood(food: CreateIngredientFood, index: number) {
workingFoodData.name = food.name; foodData.data.name = food.name;
await actions.createOne(); await foodStore.actions.createOne(foodData.data);
errors.value[index].foodError = false; errors.value[index].foodError = false;
foodData.reset();
} }
// ========================================================= // =========================================================
@ -219,16 +228,16 @@ export default defineComponent({
let ingredients = parsedIng.value.map((ing) => { let ingredients = parsedIng.value.map((ing) => {
return { return {
...ing.ingredient, ...ing.ingredient,
originalText: ing.input originalText: ing.input,
} as RecipeIngredient; } as RecipeIngredient;
}); });
ingredients = ingredients.map((ing) => { ingredients = ingredients.map((ing) => {
if (!foods.value || !units.value) { if (!foodStore.foods.value || !units.value) {
return ing; return ing;
} }
// Get food from foods // 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 // Get unit from units
ing.unit = units.value.find((u) => u.name === ing.unit?.name); ing.unit = units.value.find((u) => u.name === ing.unit?.name);
@ -252,8 +261,8 @@ export default defineComponent({
saveAll, saveAll,
createFood, createFood,
errors, errors,
actions, actions: foodStore.actions,
workingFoodData, workingFoodData: foodData,
isError, isError,
panels, panels,
asPercentage, asPercentage,

View File

@ -108,10 +108,11 @@ import { defineComponent, toRefs, computed, reactive } from "@nuxtjs/composition
import RecipeSearchFilterSelector from "~/components/Domain/Recipe/RecipeSearchFilterSelector.vue"; import RecipeSearchFilterSelector from "~/components/Domain/Recipe/RecipeSearchFilterSelector.vue";
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue"; import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.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 { RecipeSummary } from "~/types/api-types/recipe";
import { useRouteQuery } from "~/composables/use-router"; import { useRouteQuery } from "~/composables/use-router";
import { RecipeTag } from "~/types/api-types/user"; import { RecipeTag } from "~/types/api-types/user";
import { useFoodStore } from "~/composables/store";
interface GenericFilter { interface GenericFilter {
exclude: boolean; exclude: boolean;
@ -259,7 +260,7 @@ export default defineComponent({
state.foodFilter = params; state.foodFilter = params;
} }
const { foods } = useFoods(); const { foods } = useFoodStore();
return { return {
...toRefs(state), ...toRefs(state),