diff --git a/frontend/app/components/Domain/Group/GroupDataPage.vue b/frontend/app/components/Domain/Group/GroupDataPage.vue index 99b9b981a..a1a199007 100644 --- a/frontend/app/components/Domain/Group/GroupDataPage.vue +++ b/frontend/app/components/Domain/Group/GroupDataPage.vue @@ -59,9 +59,10 @@ > {{ $t("general.confirm-delete-generic") }} -

+

{{ deleteTarget.name || deleteTarget.title || deleteTarget.id }}

+
@@ -88,6 +89,7 @@ + @@ -151,7 +153,7 @@ const createDialog = defineModel("createDialog", { type: Boolean, default: false const editForm = defineModel<{ items: AutoFormItems; data: Record }>("editForm", { required: true }); const editDialog = defineModel("editDialog", { type: Boolean, default: false }); -defineProps({ +const props = defineProps({ icon: { type: String, required: true, @@ -185,6 +187,10 @@ defineProps({ type: String, default: "name", }, + onDeleteDialogOpen: { + type: Function as PropType<(items: any[]) => Promise>, + default: null, + }, }); // ============================================================ @@ -212,8 +218,11 @@ const editEventHandler = (item: any) => { const deleteTarget = ref(null); const deleteDialog = ref(false); -function deleteEventHandler(item: any) { +async function deleteEventHandler(item: any) { deleteTarget.value = item; + if (props.onDeleteDialogOpen) { + await props.onDeleteDialogOpen([item]); + } deleteDialog.value = true; } @@ -222,8 +231,11 @@ function deleteEventHandler(item: any) { const bulkDeleteTarget = ref>([]); const bulkDeleteDialog = ref(false); -function bulkDeleteEventHandler(items: Array) { +async function bulkDeleteEventHandler(items: Array) { bulkDeleteTarget.value = items; + if (props.onDeleteDialogOpen) { + await props.onDeleteDialogOpen(items); + } bulkDeleteDialog.value = true; console.log("Bulk Delete Event Handler", items); } diff --git a/frontend/app/lang/messages/en-US.json b/frontend/app/lang/messages/en-US.json index 380488373..389adeb77 100644 --- a/frontend/app/lang/messages/en-US.json +++ b/frontend/app/lang/messages/en-US.json @@ -1144,6 +1144,8 @@ }, "data-pages": { "foods": { + "delete-affects-recipes": "Warning: this food is used in {count} recipe(s). Deleting it will leave an empty ingredient in the recipe(s).", + "delete-affects-recipes-more": "View all {count} recipes", "merge-dialog-text": "Combining the selected foods will merge the source food and target food into a single food. The source food will be deleted and all of the references to the source food will be updated to point to the target food.", "merge-food-example": "Merging {food1} into {food2}", "seed-dialog-text": "Seed the database with foods based on your local language. This will create ~2700 common foods that can be used to organize your database. Foods are translated via a community effort.", diff --git a/frontend/app/pages/group/data/foods.vue b/frontend/app/pages/group/data/foods.vue index 41782c45b..17fbb77e9 100644 --- a/frontend/app/pages/group/data/foods.vue +++ b/frontend/app/pages/group/data/foods.vue @@ -69,11 +69,7 @@ - + {{ $t("data-pages.foods.seed-dialog-warning") }} @@ -112,11 +108,7 @@ :label="$t('data-pages.foods.food-label')" /> - + @@ -171,7 +161,7 @@ + + @@ -218,6 +223,7 @@ interface CreateIngredientFoodWithOnHand extends CreateIngredientFood { interface IngredientFoodWithOnHand extends IngredientFood { onHand: boolean; } + const userApi = useUserApi(); const i18n = useI18n(); const auth = useMealieAuth(); @@ -274,11 +280,14 @@ const tableHeaders: TableHeaders[] = [ ]; const userHousehold = computed(() => auth.user.value?.householdSlug || ""); +const userGroup = computed(() => auth.user.value?.groupSlug || ""); const foodStore = useFoodStore(); -const foods = computed(() => foodStore.store.value.map((food) => { - const onHand = food.householdsWithIngredientFood?.includes(userHousehold.value) || false; - return { ...food, onHand } as IngredientFoodWithOnHand; -})); +const foods = computed(() => + foodStore.store.value.map((food) => { + const onHand = food.householdsWithIngredientFood?.includes(userHousehold.value) || false; + return { ...food, onHand } as IngredientFoodWithOnHand; + }), +); // ============================================================ // Labels @@ -383,6 +392,9 @@ async function handleBulkAction(event: string, items: IngredientFoodWithOnHand[] if (event === "delete-selected") { const ids = items.map(item => item.id); await foodStore.actions.deleteMany(ids); + affectedRecipes.value = []; + affectedRecipesTotal.value = 0; + affectedRecipesMoreLink.value = ""; } else if (event === "assign-selected") { bulkAssignEventHandler(items); @@ -401,6 +413,26 @@ function updateFoodAlias(newAliases: IngredientFoodAlias[]) { aliasManagerDialog.value = false; } +// ============================================================ +// Delete Foods + +// fetch affected recipes before confirming deletion +const affectedRecipes = ref<{ name: string; slug: string; url: string }[]>([]); +const affectedRecipesTotal = ref(0); +const affectedRecipesMoreLink = ref(""); + +async function onDeleteDialogOpen(items: IngredientFoodWithOnHand[]) { + const ids = items.map(item => item.id); + const { data } = await userApi.recipes.search({ foods: ids, perPage: 5 }); + affectedRecipes.value = (data?.items ?? []).map(r => ({ + name: r.name ?? "", + slug: r.slug ?? "", + url: `/g/${userGroup.value}/r/${r.slug}`, + })); + affectedRecipesTotal.value = data?.total ?? 0; + affectedRecipesMoreLink.value = `/g/${userGroup.value}?${ids.map(id => `foods=${id}`).join("&")}`; +} + // ============================================================ // Merge Foods