mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-02 20:15:24 +02:00
feat: Recipe Actions (#3448)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com> Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
parent
ee87a14401
commit
3807778e2f
22 changed files with 860 additions and 10 deletions
|
@ -70,6 +70,7 @@
|
|||
print: true,
|
||||
printPreferences: true,
|
||||
share: loggedIn,
|
||||
recipeActions: true,
|
||||
}"
|
||||
@print="$emit('print')"
|
||||
/>
|
||||
|
|
|
@ -105,6 +105,26 @@
|
|||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<div v-if="useItems.recipeActions && recipeActions && recipeActions.length">
|
||||
<v-divider />
|
||||
<v-list-group @click.stop>
|
||||
<template #activator>
|
||||
<v-list-item-title>{{ $tc("recipe.recipe-actions") }}</v-list-item-title>
|
||||
</template>
|
||||
<v-list dense class="ma-0 pa-0">
|
||||
<v-list-item
|
||||
v-for="(action, index) in recipeActions"
|
||||
:key="index"
|
||||
class="pl-6"
|
||||
@click="executeRecipeAction(action)"
|
||||
>
|
||||
<v-list-item-title>
|
||||
{{ action.title }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-list-group>
|
||||
</div>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
|
@ -117,11 +137,12 @@ import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
|
|||
import RecipeDialogShare from "./RecipeDialogShare.vue";
|
||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { useGroupRecipeActions } from "~/composables/use-group-recipe-actions";
|
||||
import { useGroupSelf } from "~/composables/use-groups";
|
||||
import { alert } from "~/composables/use-toast";
|
||||
import { usePlanTypeOptions } from "~/composables/use-group-mealplan";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import { ShoppingListSummary } from "~/lib/api/types/group";
|
||||
import { GroupRecipeActionOut, ShoppingListSummary } from "~/lib/api/types/group";
|
||||
import { PlanEntryType } from "~/lib/api/types/meal-plan";
|
||||
import { useAxiosDownloader } from "~/composables/api/use-axios-download";
|
||||
|
||||
|
@ -134,6 +155,7 @@ export interface ContextMenuIncludes {
|
|||
print: boolean;
|
||||
printPreferences: boolean;
|
||||
share: boolean;
|
||||
recipeActions: boolean;
|
||||
}
|
||||
|
||||
export interface ContextMenuItem {
|
||||
|
@ -163,6 +185,7 @@ export default defineComponent({
|
|||
print: true,
|
||||
printPreferences: true,
|
||||
share: true,
|
||||
recipeActions: true,
|
||||
}),
|
||||
},
|
||||
// Append items are added at the end of the useItems list
|
||||
|
@ -347,6 +370,19 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const router = useRouter();
|
||||
const groupRecipeActionsStore = useGroupRecipeActions();
|
||||
|
||||
async function executeRecipeAction(action: GroupRecipeActionOut) {
|
||||
const response = await groupRecipeActionsStore.execute(action, props.recipe);
|
||||
|
||||
if (action.actionType === "post") {
|
||||
if (!response || (response.status >= 200 && response.status < 300)) {
|
||||
alert.success(i18n.tc("events.message-sent"));
|
||||
} else {
|
||||
alert.error(i18n.tc("events.something-went-wrong"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRecipe() {
|
||||
await api.recipes.deleteOne(props.slug);
|
||||
|
@ -437,6 +473,8 @@ export default defineComponent({
|
|||
...toRefs(state),
|
||||
recipeRef,
|
||||
recipeRefWithScale,
|
||||
executeRecipeAction,
|
||||
recipeActions: groupRecipeActionsStore.recipeActions,
|
||||
shoppingLists,
|
||||
duplicateRecipe,
|
||||
contextMenuEventHandler,
|
||||
|
|
98
frontend/composables/use-group-recipe-actions.ts
Normal file
98
frontend/composables/use-group-recipe-actions.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
import { computed, reactive, ref } from "@nuxtjs/composition-api";
|
||||
import { useStoreActions } from "./partials/use-actions-factory";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
import { GroupRecipeActionOut, RecipeActionType } from "~/lib/api/types/group";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
|
||||
const groupRecipeActions = ref<GroupRecipeActionOut[] | null>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
export function useGroupRecipeActionData() {
|
||||
const data = reactive({
|
||||
id: "",
|
||||
actionType: "link" as RecipeActionType,
|
||||
title: "",
|
||||
url: "",
|
||||
});
|
||||
|
||||
function reset() {
|
||||
data.id = "";
|
||||
data.actionType = "link";
|
||||
data.title = "";
|
||||
data.url = "";
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
|
||||
export const useGroupRecipeActions = function (
|
||||
orderBy: string | null = "title",
|
||||
orderDirection: string | null = "asc",
|
||||
) {
|
||||
const api = useUserApi();
|
||||
|
||||
async function refreshGroupRecipeActions() {
|
||||
loading.value = true;
|
||||
const { data } = await api.groupRecipeActions.getAll(1, -1, { orderBy, orderDirection });
|
||||
groupRecipeActions.value = data?.items || null;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const recipeActions = computed<GroupRecipeActionOut[] | null>(() => {
|
||||
return groupRecipeActions.value;
|
||||
});
|
||||
|
||||
function parseRecipeActionUrl(url: string, recipe: Recipe): string {
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
return url
|
||||
.replace("${url}", window.location.href)
|
||||
.replace("${id}", recipe.id || "")
|
||||
.replace("${slug}", recipe.slug || "")
|
||||
/* eslint-enable no-template-curly-in-string */
|
||||
};
|
||||
|
||||
async function execute(action: GroupRecipeActionOut, recipe: Recipe): Promise<void | Response> {
|
||||
const url = parseRecipeActionUrl(action.url, recipe);
|
||||
|
||||
switch (action.actionType) {
|
||||
case "link":
|
||||
window.open(url, "_blank")?.focus();
|
||||
break;
|
||||
case "post":
|
||||
return await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
// The "text/plain" content type header is used here to skip the CORS preflight request,
|
||||
// since it may fail. This is fine, since we don't care about the response, we just want
|
||||
// the request to get sent.
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
body: JSON.stringify(recipe),
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (!groupRecipeActions.value && !loading.value) {
|
||||
refreshGroupRecipeActions();
|
||||
};
|
||||
|
||||
const actions = {
|
||||
...useStoreActions<GroupRecipeActionOut>(api.groupRecipeActions, groupRecipeActions, loading),
|
||||
flushStore() {
|
||||
groupRecipeActions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
actions,
|
||||
execute,
|
||||
recipeActions,
|
||||
};
|
||||
};
|
|
@ -64,6 +64,7 @@
|
|||
"something-went-wrong": "Something Went Wrong!",
|
||||
"subscribed-events": "Subscribed Events",
|
||||
"test-message-sent": "Test Message Sent",
|
||||
"message-sent": "Message Sent",
|
||||
"new-notification": "New Notification",
|
||||
"event-notifiers": "Event Notifiers",
|
||||
"apprise-url-skipped-if-blank": "Apprise URL (skipped if blank)",
|
||||
|
@ -160,6 +161,7 @@
|
|||
"test": "Test",
|
||||
"themes": "Themes",
|
||||
"thursday": "Thursday",
|
||||
"title": "Title",
|
||||
"token": "Token",
|
||||
"tuesday": "Tuesday",
|
||||
"type": "Type",
|
||||
|
@ -582,7 +584,8 @@
|
|||
"upload-image": "Upload image",
|
||||
"screen-awake": "Keep Screen Awake",
|
||||
"remove-image": "Remove image",
|
||||
"nextStep": "Next step"
|
||||
"nextStep": "Next step",
|
||||
"recipe-actions": "Recipe Actions"
|
||||
},
|
||||
"search": {
|
||||
"advanced-search": "Advanced Search",
|
||||
|
@ -1001,6 +1004,12 @@
|
|||
"delete-recipes": "Delete Recipes",
|
||||
"source-unit-will-be-deleted": "Source Unit will be deleted"
|
||||
},
|
||||
"recipe-actions": {
|
||||
"recipe-actions-data": "Recipe Actions Data",
|
||||
"new-recipe-action": "New Recipe Action",
|
||||
"edit-recipe-action": "Edit Recipe Action",
|
||||
"action-type": "Action Type"
|
||||
},
|
||||
"create-alias": "Create Alias",
|
||||
"manage-aliases": "Manage Aliases",
|
||||
"seed-data": "Seed Data",
|
||||
|
|
|
@ -9,6 +9,7 @@ import { UtilsAPI } from "./user/utils";
|
|||
import { FoodAPI } from "./user/recipe-foods";
|
||||
import { UnitAPI } from "./user/recipe-units";
|
||||
import { CookbookAPI } from "./user/group-cookbooks";
|
||||
import { GroupRecipeActionsAPI } from "./user/group-recipe-actions";
|
||||
import { WebhooksAPI } from "./user/group-webhooks";
|
||||
import { RegisterAPI } from "./user/user-registration";
|
||||
import { MealPlanAPI } from "./user/group-mealplan";
|
||||
|
@ -36,6 +37,7 @@ export class UserApiClient {
|
|||
public foods: FoodAPI;
|
||||
public units: UnitAPI;
|
||||
public cookbooks: CookbookAPI;
|
||||
public groupRecipeActions: GroupRecipeActionsAPI;
|
||||
public groupWebhooks: WebhooksAPI;
|
||||
public register: RegisterAPI;
|
||||
public mealplans: MealPlanAPI;
|
||||
|
@ -65,6 +67,7 @@ export class UserApiClient {
|
|||
this.users = new UserApi(requests);
|
||||
this.groups = new GroupAPI(requests);
|
||||
this.cookbooks = new CookbookAPI(requests);
|
||||
this.groupRecipeActions = new GroupRecipeActionsAPI(requests);
|
||||
this.groupWebhooks = new WebhooksAPI(requests);
|
||||
this.register = new RegisterAPI(requests);
|
||||
this.mealplans = new MealPlanAPI(requests);
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
/* Do not modify it by hand - just update the pydantic models and then re-run the script
|
||||
*/
|
||||
|
||||
export type RecipeActionType =
|
||||
| "link"
|
||||
| "post";
|
||||
export type WebhookType = "mealplan";
|
||||
export type SupportedMigrations =
|
||||
| "nextcloud"
|
||||
|
@ -26,6 +29,11 @@ export interface CreateGroupPreferences {
|
|||
recipeDisableAmount?: boolean;
|
||||
groupId: string;
|
||||
}
|
||||
export interface CreateGroupRecipeAction {
|
||||
actionType: RecipeActionType;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
export interface CreateInviteToken {
|
||||
uses: number;
|
||||
}
|
||||
|
@ -191,6 +199,13 @@ export interface GroupEventNotifierUpdate {
|
|||
options?: GroupEventNotifierOptions;
|
||||
id: string;
|
||||
}
|
||||
export interface GroupRecipeActionOut {
|
||||
actionType: RecipeActionType;
|
||||
title: string;
|
||||
url: string;
|
||||
groupId: string;
|
||||
id: string;
|
||||
}
|
||||
export interface GroupStatistics {
|
||||
totalRecipes: number;
|
||||
totalUsers: number;
|
||||
|
@ -230,6 +245,12 @@ export interface ReadWebhook {
|
|||
groupId: string;
|
||||
id: string;
|
||||
}
|
||||
export interface SaveGroupRecipeAction {
|
||||
actionType: RecipeActionType;
|
||||
title: string;
|
||||
url: string;
|
||||
groupId: string;
|
||||
}
|
||||
export interface SaveInviteToken {
|
||||
usesLeft: number;
|
||||
groupId: string;
|
||||
|
|
14
frontend/lib/api/user/group-recipe-actions.ts
Normal file
14
frontend/lib/api/user/group-recipe-actions.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { BaseCRUDAPI } from "../base/base-clients";
|
||||
import { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/group";
|
||||
|
||||
const prefix = "/api";
|
||||
|
||||
const routes = {
|
||||
groupRecipeActions: `${prefix}/groups/recipe-actions`,
|
||||
groupRecipeActionsId: (id: string | number) => `${prefix}/groups/recipe-actions/${id}`,
|
||||
};
|
||||
|
||||
export class GroupRecipeActionsAPI extends BaseCRUDAPI<CreateGroupRecipeAction, GroupRecipeActionOut> {
|
||||
baseRoute = routes.groupRecipeActions;
|
||||
itemRoute = routes.groupRecipeActionsId;
|
||||
}
|
|
@ -41,6 +41,7 @@ export default defineComponent({
|
|||
const { i18n } = useContext();
|
||||
const buttonLookup: { [key: string]: string } = {
|
||||
recipes: i18n.tc("general.recipes"),
|
||||
recipeActions: i18n.tc("recipe.recipe-actions"),
|
||||
foods: i18n.tc("general.foods"),
|
||||
units: i18n.tc("general.units"),
|
||||
labels: i18n.tc("data-pages.labels.labels"),
|
||||
|
@ -56,6 +57,11 @@ export default defineComponent({
|
|||
text: i18n.t("general.recipes"),
|
||||
value: "new",
|
||||
to: "/group/data/recipes",
|
||||
},
|
||||
{
|
||||
text: i18n.t("recipe.recipe-actions"),
|
||||
value: "new",
|
||||
to: "/group/data/recipe-actions",
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
|
@ -92,7 +98,13 @@ export default defineComponent({
|
|||
]);
|
||||
|
||||
const buttonText = computed(() => {
|
||||
const last = route.value.path.split("/").pop();
|
||||
const last = route.value.path
|
||||
.split("/")
|
||||
.pop()
|
||||
// convert hypenated-values to camelCase
|
||||
?.replace(/-([a-z])/g, function (g) {
|
||||
return g[1].toUpperCase();
|
||||
})
|
||||
|
||||
if (last) {
|
||||
return buttonLookup[last];
|
||||
|
|
265
frontend/pages/group/data/recipe-actions.vue
Normal file
265
frontend/pages/group/data/recipe-actions.vue
Normal file
|
@ -0,0 +1,265 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Create Dialog -->
|
||||
<BaseDialog
|
||||
v-model="state.createDialog"
|
||||
:title="$t('data-pages.recipe-actions.new-recipe-action')"
|
||||
:icon="$globals.icons.primary"
|
||||
@submit="createAction"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-form ref="domNewActionForm">
|
||||
<v-text-field
|
||||
v-model="createTarget.title"
|
||||
autofocus
|
||||
:label="$t('general.title')"
|
||||
:rules="[validators.required]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="createTarget.url"
|
||||
:label="$t('general.url')"
|
||||
:rules="[validators.required]"
|
||||
/>
|
||||
<v-select
|
||||
v-model="createTarget.actionType"
|
||||
:items="actionTypeOptions"
|
||||
:label="$t('data-pages.recipe-actions.action-type')"
|
||||
:rules="[validators.required]"
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<BaseDialog
|
||||
v-model="state.editDialog"
|
||||
:icon="$globals.icons.primary"
|
||||
:title="$t('data-pages.recipe-actions.edit-recipe-action')"
|
||||
:submit-text="$tc('general.save')"
|
||||
@submit="editSaveAction"
|
||||
>
|
||||
<v-card-text v-if="editTarget">
|
||||
<div class="mt-4">
|
||||
<v-text-field v-model="editTarget.title" :label="$t('general.title')"/>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<v-text-field v-model="editTarget.url" :label="$t('general.url')"/>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<v-select
|
||||
v-model="editTarget.actionType"
|
||||
:items="actionTypeOptions"
|
||||
:label="$t('data-pages.recipe-actions.action-type')"
|
||||
/>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Delete Dialog -->
|
||||
<BaseDialog
|
||||
v-model="state.deleteDialog"
|
||||
:title="$tc('general.confirm')"
|
||||
:icon="$globals.icons.alertCircle"
|
||||
color="error"
|
||||
@confirm="deleteAction"
|
||||
>
|
||||
<v-card-text>
|
||||
{{ $t("general.confirm-delete-generic") }}
|
||||
<p v-if="deleteTarget" class="mt-4 ml-4">{{ deleteTarget.title }}</p>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Bulk Delete Dialog -->
|
||||
<BaseDialog
|
||||
v-model="state.bulkDeleteDialog"
|
||||
width="650px"
|
||||
:title="$tc('general.confirm')"
|
||||
:icon="$globals.icons.alertCircle"
|
||||
color="error"
|
||||
@confirm="deleteSelected"
|
||||
>
|
||||
<v-card-text>
|
||||
<p class="h4">{{ $t('general.confirm-delete-generic-items') }}</p>
|
||||
<v-card outlined>
|
||||
<v-virtual-scroll height="400" item-height="25" :items="bulkDeleteTarget">
|
||||
<template #default="{ item }">
|
||||
<v-list-item class="pb-2">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</v-card>
|
||||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Data Table -->
|
||||
<BaseCardSectionTitle :icon="$globals.icons.primary" section :title="$tc('data-pages.recipe-actions.recipe-actions-data')"> </BaseCardSectionTitle>
|
||||
<CrudTable
|
||||
:table-config="tableConfig"
|
||||
:headers.sync="tableHeaders"
|
||||
:data="actions || []"
|
||||
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
|
||||
@delete-one="deleteEventHandler"
|
||||
@edit-one="editEventHandler"
|
||||
@delete-selected="bulkDeleteEventHandler"
|
||||
>
|
||||
<template #button-row>
|
||||
<BaseButton create @click="state.createDialog = true">{{ $t("general.create") }}</BaseButton>
|
||||
</template>
|
||||
<template #item.onHand="{ item }">
|
||||
<v-icon :color="item.onHand ? 'success' : undefined">
|
||||
{{ item.onHand ? $globals.icons.check : $globals.icons.close }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</CrudTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { useGroupRecipeActions, useGroupRecipeActionData } from "~/composables/use-group-recipe-actions";
|
||||
import { GroupRecipeActionOut } from "~/lib/api/types/group";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { i18n } = useContext();
|
||||
const tableConfig = {
|
||||
hideColumns: true,
|
||||
canExport: true,
|
||||
};
|
||||
const tableHeaders = [
|
||||
{
|
||||
text: i18n.t("general.id"),
|
||||
value: "id",
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
text: i18n.t("general.title"),
|
||||
value: "title",
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
text: i18n.t("general.url"),
|
||||
value: "url",
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
text: i18n.t("data-pages.recipe-actions.action-type"),
|
||||
value: "actionType",
|
||||
show: true,
|
||||
},
|
||||
];
|
||||
|
||||
const state = reactive({
|
||||
createDialog: false,
|
||||
editDialog: false,
|
||||
deleteDialog: false,
|
||||
bulkDeleteDialog: false,
|
||||
});
|
||||
|
||||
const actionData = useGroupRecipeActionData();
|
||||
const actionStore = useGroupRecipeActions(null, null);
|
||||
const actionTypeOptions = ["link", "post"]
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Create Action
|
||||
|
||||
async function createAction() {
|
||||
// @ts-ignore groupId isn't required
|
||||
await actionStore.actions.createOne({
|
||||
actionType: actionData.data.actionType,
|
||||
title: actionData.data.title,
|
||||
url: actionData.data.url,
|
||||
});
|
||||
actionData.reset();
|
||||
state.createDialog = false;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Edit Action
|
||||
|
||||
const editTarget = ref<GroupRecipeActionOut | null>(null);
|
||||
|
||||
function editEventHandler(item: GroupRecipeActionOut) {
|
||||
state.editDialog = true;
|
||||
editTarget.value = item;
|
||||
}
|
||||
|
||||
async function editSaveAction() {
|
||||
if (!editTarget.value) {
|
||||
return;
|
||||
}
|
||||
await actionStore.actions.updateOne(editTarget.value);
|
||||
state.editDialog = false;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Delete Action
|
||||
|
||||
const deleteTarget = ref<GroupRecipeActionOut | null>(null);
|
||||
|
||||
function deleteEventHandler(item: GroupRecipeActionOut) {
|
||||
state.deleteDialog = true;
|
||||
deleteTarget.value = item;
|
||||
}
|
||||
|
||||
async function deleteAction() {
|
||||
if (!deleteTarget.value || deleteTarget.value.id === undefined) {
|
||||
return;
|
||||
}
|
||||
await actionStore.actions.deleteOne(deleteTarget.value.id);
|
||||
state.deleteDialog = false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Bulk Delete Action
|
||||
|
||||
const bulkDeleteTarget = ref<GroupRecipeActionOut[]>([]);
|
||||
function bulkDeleteEventHandler(selection: GroupRecipeActionOut[]) {
|
||||
bulkDeleteTarget.value = selection;
|
||||
state.bulkDeleteDialog = true;
|
||||
}
|
||||
|
||||
async function deleteSelected() {
|
||||
for (const item of bulkDeleteTarget.value) {
|
||||
await actionStore.actions.deleteOne(item.id);
|
||||
}
|
||||
bulkDeleteTarget.value = [];
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
tableConfig,
|
||||
tableHeaders,
|
||||
actionTypeOptions,
|
||||
actions: actionStore.recipeActions,
|
||||
validators,
|
||||
|
||||
// create
|
||||
createTarget: actionData.data,
|
||||
createAction,
|
||||
|
||||
// edit
|
||||
editTarget,
|
||||
editEventHandler,
|
||||
editSaveAction,
|
||||
|
||||
// delete
|
||||
deleteTarget,
|
||||
deleteEventHandler,
|
||||
deleteAction,
|
||||
|
||||
// bulk delete
|
||||
bulkDeleteTarget,
|
||||
bulkDeleteEventHandler,
|
||||
deleteSelected,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue