mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-06-22 23:12:16 -04:00
feat(frontend): ✨ Create CRUD User Interface for Units and Foods
This commit is contained in:
parent
122d35ec09
commit
a1aad078da
@ -10,39 +10,16 @@ export interface CrudAPIInterface {
|
|||||||
// Methods
|
// Methods
|
||||||
}
|
}
|
||||||
|
|
||||||
export const crudMixins = <T>(
|
export interface CrudAPIMethodsInterface {
|
||||||
requests: ApiRequestInstance,
|
// CRUD Methods
|
||||||
baseRoute: string,
|
getAll(): any
|
||||||
itemRoute: (itemId: string) => string
|
createOne(): any
|
||||||
) => {
|
getOne(): any
|
||||||
async function getAll(start = 0, limit = 9999) {
|
updateOne(): any
|
||||||
return await requests.get<T[]>(baseRoute, {
|
patchOne(): any
|
||||||
params: { start, limit },
|
deleteOne(): any
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createOne(payload: T) {
|
|
||||||
return await requests.post<T>(baseRoute, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getOne(itemId: string) {
|
|
||||||
return await requests.get<T>(itemRoute(itemId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateOne(itemId: string, payload: T) {
|
|
||||||
return await requests.put<T>(itemRoute(itemId), payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function patchOne(itemId: string, payload: T) {
|
|
||||||
return await requests.patch(itemRoute(itemId), payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteOne(itemId: string) {
|
|
||||||
return await requests.delete<T>(itemRoute(itemId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return { getAll, getOne, updateOne, patchOne, deleteOne, createOne };
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class BaseAPI {
|
export abstract class BaseAPI {
|
||||||
requests: ApiRequestInstance;
|
requests: ApiRequestInstance;
|
||||||
@ -66,11 +43,11 @@ export abstract class BaseCRUDAPI<T, U> extends BaseAPI implements CrudAPIInterf
|
|||||||
return await this.requests.post<T>(this.baseRoute, payload);
|
return await this.requests.post<T>(this.baseRoute, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOne(itemId: string) {
|
async getOne(itemId: string | number) {
|
||||||
return await this.requests.get<T>(this.itemRoute(itemId));
|
return await this.requests.get<T>(this.itemRoute(itemId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOne(itemId: string, payload: T) {
|
async updateOne(itemId: string | number, payload: T) {
|
||||||
return await this.requests.put<T>(this.itemRoute(itemId), payload);
|
return await this.requests.put<T>(this.itemRoute(itemId), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { requests } from "../requests";
|
|
||||||
import { BaseCRUDAPI } from "./_base";
|
import { BaseCRUDAPI } from "./_base";
|
||||||
|
|
||||||
export type EventCategory = "general" | "recipe" | "backup" | "scheduled" | "migration" | "group" | "user";
|
export type EventCategory = "general" | "recipe" | "backup" | "scheduled" | "migration" | "group" | "user";
|
||||||
@ -36,7 +35,7 @@ export class NotificationsAPI extends BaseCRUDAPI<EventNotification, CreateEvent
|
|||||||
itemRoute = routes.aboutEventsNotificationsId;
|
itemRoute = routes.aboutEventsNotificationsId;
|
||||||
/** Returns the Group Data for the Current User
|
/** Returns the Group Data for the Current User
|
||||||
*/
|
*/
|
||||||
async testNotification(id: number) {
|
async testNotification(id: number | null = null, testUrl: string | null = null) {
|
||||||
return await requests.post(routes.aboutEventsNotificationsTest, { id });
|
return await this.requests.post(routes.aboutEventsNotificationsTest, { id, testUrl });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
frontend/api/class-interfaces/recipe-foods.ts
Normal file
22
frontend/api/class-interfaces/recipe-foods.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { BaseCRUDAPI } from "./_base";
|
||||||
|
|
||||||
|
const prefix = "/api";
|
||||||
|
|
||||||
|
export interface CreateFood {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Food extends CreateFood {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = {
|
||||||
|
food: `${prefix}/foods`,
|
||||||
|
foodsFood: (tag: string) => `${prefix}/foods/${tag}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class FoodAPI extends BaseCRUDAPI<Food, CreateFood> {
|
||||||
|
baseRoute: string = routes.food;
|
||||||
|
itemRoute = routes.foodsFood;
|
||||||
|
}
|
23
frontend/api/class-interfaces/recipe-units.ts
Normal file
23
frontend/api/class-interfaces/recipe-units.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { BaseCRUDAPI } from "./_base";
|
||||||
|
|
||||||
|
const prefix = "/api";
|
||||||
|
|
||||||
|
export interface CreateUnit {
|
||||||
|
name: string;
|
||||||
|
abbreviation: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Unit extends CreateUnit {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = {
|
||||||
|
unit: `${prefix}/units`,
|
||||||
|
unitsUnit: (tag: string) => `${prefix}/units/${tag}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UnitAPI extends BaseCRUDAPI<Unit, CreateUnit> {
|
||||||
|
baseRoute: string = routes.unit;
|
||||||
|
itemRoute = routes.unitsUnit;
|
||||||
|
}
|
@ -9,6 +9,8 @@ import { CategoriesAPI } from "./class-interfaces/categories";
|
|||||||
import { TagsAPI } from "./class-interfaces/tags";
|
import { TagsAPI } from "./class-interfaces/tags";
|
||||||
import { UtilsAPI } from "./class-interfaces/utils";
|
import { UtilsAPI } from "./class-interfaces/utils";
|
||||||
import { NotificationsAPI } from "./class-interfaces/event-notifications";
|
import { NotificationsAPI } from "./class-interfaces/event-notifications";
|
||||||
|
import { FoodAPI } from "./class-interfaces/recipe-foods";
|
||||||
|
import { UnitAPI } from "./class-interfaces/recipe-units";
|
||||||
import { ApiRequestInstance } from "~/types/api";
|
import { ApiRequestInstance } from "~/types/api";
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
@ -23,6 +25,8 @@ class Api {
|
|||||||
public tags: TagsAPI;
|
public tags: TagsAPI;
|
||||||
public utils: UtilsAPI;
|
public utils: UtilsAPI;
|
||||||
public notifications: NotificationsAPI;
|
public notifications: NotificationsAPI;
|
||||||
|
public foods: FoodAPI;
|
||||||
|
public units: UnitAPI;
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
public upload: UploadFile;
|
public upload: UploadFile;
|
||||||
@ -36,6 +40,8 @@ class Api {
|
|||||||
this.recipes = new RecipeAPI(requests);
|
this.recipes = new RecipeAPI(requests);
|
||||||
this.categories = new CategoriesAPI(requests);
|
this.categories = new CategoriesAPI(requests);
|
||||||
this.tags = new TagsAPI(requests);
|
this.tags = new TagsAPI(requests);
|
||||||
|
this.units = new UnitAPI(requests);
|
||||||
|
this.foods = new FoodAPI(requests);
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
this.users = new UserApi(requests);
|
this.users = new UserApi(requests);
|
||||||
|
@ -62,12 +62,14 @@ export const useNotifications = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testById() {
|
async function testById(id: number) {
|
||||||
// TODO: Test by ID
|
const {data} = await api.notifications.testNotification(id, null)
|
||||||
|
console.log(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testByUrl() {
|
async function testByUrl(testUrl: string) {
|
||||||
// TODO: Test by URL
|
const {data} = await api.notifications.testNotification(null, testUrl)
|
||||||
|
console.log(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const notifications = getNotifications();
|
const notifications = getNotifications();
|
||||||
|
91
frontend/composables/use-recipe-foods.ts
Normal file
91
frontend/composables/use-recipe-foods.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { useAsync, ref, reactive } from "@nuxtjs/composition-api";
|
||||||
|
import { useAsyncKey } from "./use-utils";
|
||||||
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
import { Food } from "~/api/class-interfaces/recipe-foods";
|
||||||
|
|
||||||
|
export const useFoods = function () {
|
||||||
|
const api = useApiSingleton();
|
||||||
|
const loading = ref(false);
|
||||||
|
const deleteTargetId = ref(0);
|
||||||
|
const validForm = ref(true);
|
||||||
|
|
||||||
|
const workingFoodData = reactive({
|
||||||
|
id: 0,
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
foods.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 && foods.value) {
|
||||||
|
foods.value.push(data);
|
||||||
|
} else {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
domForm?.reset();
|
||||||
|
validForm.value = true;
|
||||||
|
this.resetWorking();
|
||||||
|
loading.value = false;
|
||||||
|
},
|
||||||
|
async updateOne() {
|
||||||
|
if (!workingFoodData.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.foods.updateOne(workingFoodData.id, workingFoodData);
|
||||||
|
if (data && foods.value) {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
},
|
||||||
|
async deleteOne(id: string | number) {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.foods.deleteOne(id);
|
||||||
|
if (data && foods.value) {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetWorking() {
|
||||||
|
workingFoodData.id = 0;
|
||||||
|
workingFoodData.name = "";
|
||||||
|
workingFoodData.description = "";
|
||||||
|
},
|
||||||
|
setWorking(item: Food) {
|
||||||
|
workingFoodData.id = item.id;
|
||||||
|
workingFoodData.name = item.name;
|
||||||
|
workingFoodData.description = item.description;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const foods = actions.getAll();
|
||||||
|
|
||||||
|
return { foods, workingFoodData, deleteTargetId, actions, validForm };
|
||||||
|
};
|
94
frontend/composables/use-recipe-units.ts
Normal file
94
frontend/composables/use-recipe-units.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { useAsync, ref, reactive } from "@nuxtjs/composition-api";
|
||||||
|
import { useAsyncKey } from "./use-utils";
|
||||||
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
import { Unit } from "~/api/class-interfaces/recipe-units";
|
||||||
|
|
||||||
|
export const useUnits = function () {
|
||||||
|
const api = useApiSingleton();
|
||||||
|
const loading = ref(false);
|
||||||
|
const deleteTargetId = ref(0);
|
||||||
|
const validForm = ref(true);
|
||||||
|
|
||||||
|
const workingUnitData = reactive({
|
||||||
|
id: 0,
|
||||||
|
name: "",
|
||||||
|
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) {
|
||||||
|
units.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 && units.value) {
|
||||||
|
units.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 && units.value) {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
},
|
||||||
|
async deleteOne(id: string | number) {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.units.deleteOne(id);
|
||||||
|
if (data && units.value) {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetWorking() {
|
||||||
|
workingUnitData.id = 0;
|
||||||
|
workingUnitData.name = "";
|
||||||
|
workingUnitData.abbreviation = "";
|
||||||
|
workingUnitData.description = "";
|
||||||
|
},
|
||||||
|
setWorking(item: Unit) {
|
||||||
|
workingUnitData.id = item.id;
|
||||||
|
workingUnitData.name = item.name;
|
||||||
|
workingUnitData.abbreviation = item.abbreviation;
|
||||||
|
workingUnitData.description = item.description;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const units = actions.getAll();
|
||||||
|
|
||||||
|
return { units, workingUnitData, deleteTargetId, actions, validForm };
|
||||||
|
};
|
@ -83,6 +83,16 @@ export default defineComponent({
|
|||||||
to: "/admin/toolbox/notifications",
|
to: "/admin/toolbox/notifications",
|
||||||
title: this.$t("events.notification"),
|
title: this.$t("events.notification"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: this.$globals.icons.foods,
|
||||||
|
to: "/admin/toolbox/foods",
|
||||||
|
title: "Manage Foods",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: this.$globals.icons.units,
|
||||||
|
to: "/admin/toolbox/units",
|
||||||
|
title: "Manage Units",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: this.$globals.icons.tags,
|
icon: this.$globals.icons.tags,
|
||||||
to: "/admin/toolbox/categories",
|
to: "/admin/toolbox/categories",
|
||||||
|
120
frontend/pages/admin/toolbox/foods.vue
Normal file
120
frontend/pages/admin/toolbox/foods.vue
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<BaseCardSectionTitle title="Manage Units"> </BaseCardSectionTitle>
|
||||||
|
<v-toolbar flat>
|
||||||
|
<BaseDialog
|
||||||
|
ref="domFoodDialog"
|
||||||
|
:title="dialog.title"
|
||||||
|
:icon="$globals.icons.units"
|
||||||
|
:submit-text="dialog.text"
|
||||||
|
:keep-open="!validForm"
|
||||||
|
@submit="create ? actions.createOne(domCreateFoodForm) : actions.updateOne()"
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form ref="domCreateFoodForm">
|
||||||
|
<v-text-field v-model="workingFoodData.name" label="Name" :rules="[validators.required]"></v-text-field>
|
||||||
|
<v-text-field v-model="workingFoodData.description" label="Description"></v-text-field>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</BaseDialog>
|
||||||
|
|
||||||
|
<BaseButton
|
||||||
|
class="mr-1"
|
||||||
|
@click="
|
||||||
|
create = true;
|
||||||
|
actions.resetWorking();
|
||||||
|
domFoodDialog.open();
|
||||||
|
"
|
||||||
|
></BaseButton>
|
||||||
|
<BaseButton secondary @click="filter = !filter"> Filter </BaseButton>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-expand-transition>
|
||||||
|
<div v-show="filter">
|
||||||
|
<v-text-field v-model="search" style="max-width: 500px" label="Filter" class="ml-4"> </v-text-field>
|
||||||
|
</div>
|
||||||
|
</v-expand-transition>
|
||||||
|
|
||||||
|
<v-data-table :headers="headers" :items="foods || []" item-key="id" class="elevation-0" :search="search">
|
||||||
|
<template #item.actions="{ item }">
|
||||||
|
<div class="d-flex justify-end">
|
||||||
|
<BaseButton
|
||||||
|
edit
|
||||||
|
small
|
||||||
|
class="mr-2"
|
||||||
|
@click="
|
||||||
|
create = false;
|
||||||
|
actions.setWorking(item);
|
||||||
|
domFoodDialog.open();
|
||||||
|
"
|
||||||
|
></BaseButton>
|
||||||
|
<BaseDialog :title="$t('general.confirm')" color="error" @confirm="actions.deleteOne(item.id)">
|
||||||
|
<template #activator="{ open }">
|
||||||
|
<BaseButton delete small @click="open"></BaseButton>
|
||||||
|
</template>
|
||||||
|
<v-card-text>
|
||||||
|
{{ $t("general.confirm-delete-generic") }}
|
||||||
|
</v-card-text>
|
||||||
|
</BaseDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, reactive, toRefs, ref, computed } from "@nuxtjs/composition-api";
|
||||||
|
import { useFoods } from "~/composables/use-recipe-foods";
|
||||||
|
import { validators } from "~/composables/use-validators";
|
||||||
|
export default defineComponent({
|
||||||
|
layout: "admin",
|
||||||
|
setup() {
|
||||||
|
const { foods, actions, workingFoodData, validForm } = useFoods();
|
||||||
|
|
||||||
|
const domCreateFoodForm = ref(null);
|
||||||
|
const domFoodDialog = ref(null);
|
||||||
|
|
||||||
|
const dialog = computed(() => {
|
||||||
|
if (state.create) {
|
||||||
|
return {
|
||||||
|
title: "Create Food",
|
||||||
|
text: "Create",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
title: "Edit Food",
|
||||||
|
text: "Update",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
headers: [
|
||||||
|
{ text: "Id", value: "id" },
|
||||||
|
{ text: "Name", value: "name" },
|
||||||
|
{ text: "Description", value: "description" },
|
||||||
|
{ text: "", value: "actions", sortable: false },
|
||||||
|
],
|
||||||
|
filter: false,
|
||||||
|
create: true,
|
||||||
|
search: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
actions,
|
||||||
|
dialog,
|
||||||
|
domCreateFoodForm,
|
||||||
|
domFoodDialog,
|
||||||
|
foods,
|
||||||
|
validators,
|
||||||
|
validForm,
|
||||||
|
workingFoodData,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
@ -47,7 +47,12 @@
|
|||||||
:label="$t('events.apprise-url')"
|
:label="$t('events.apprise-url')"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
|
||||||
<BaseButton class="d-flex ml-auto" small color="info" @click="testByUrl(newNotification.notificationUrl)">
|
<BaseButton
|
||||||
|
class="d-flex ml-auto"
|
||||||
|
small
|
||||||
|
color="info"
|
||||||
|
@click="testByUrl(createNotificationData.notificationUrl)"
|
||||||
|
>
|
||||||
<template #icon> {{ $globals.icons.testTube }}</template>
|
<template #icon> {{ $globals.icons.testTube }}</template>
|
||||||
{{ $t("general.test") }}
|
{{ $t("general.test") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
|
122
frontend/pages/admin/toolbox/units.vue
Normal file
122
frontend/pages/admin/toolbox/units.vue
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<BaseCardSectionTitle title="Manage Units"> </BaseCardSectionTitle>
|
||||||
|
<v-toolbar flat>
|
||||||
|
<BaseDialog
|
||||||
|
ref="domUnitDialog"
|
||||||
|
:title="dialog.title"
|
||||||
|
:icon="$globals.icons.units"
|
||||||
|
:submit-text="dialog.text"
|
||||||
|
:keep-open="!validForm"
|
||||||
|
@submit="create ? actions.createOne(domCreateUnitForm) : actions.updateOne()"
|
||||||
|
>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form ref="domCreateUnitForm">
|
||||||
|
<v-text-field v-model="workingUnitData.name" label="Name" :rules="[validators.required]"></v-text-field>
|
||||||
|
<v-text-field v-model="workingUnitData.abbreviation" label="Abbreviation"></v-text-field>
|
||||||
|
<v-text-field v-model="workingUnitData.description" label="Description"></v-text-field>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</BaseDialog>
|
||||||
|
|
||||||
|
<BaseButton
|
||||||
|
class="mr-1"
|
||||||
|
@click="
|
||||||
|
create = true;
|
||||||
|
actions.resetWorking();
|
||||||
|
domUnitDialog.open();
|
||||||
|
"
|
||||||
|
></BaseButton>
|
||||||
|
<BaseButton secondary @click="filter = !filter"> Filter </BaseButton>
|
||||||
|
</v-toolbar>
|
||||||
|
|
||||||
|
<v-expand-transition>
|
||||||
|
<div v-show="filter">
|
||||||
|
<v-text-field v-model="search" style="max-width: 500px" label="Filter" class="ml-4"> </v-text-field>
|
||||||
|
</div>
|
||||||
|
</v-expand-transition>
|
||||||
|
|
||||||
|
<v-data-table :headers="headers" :items="units || []" item-key="id" class="elevation-0" :search="search">
|
||||||
|
<template #item.actions="{ item }">
|
||||||
|
<div class="d-flex justify-end">
|
||||||
|
<BaseButton
|
||||||
|
edit
|
||||||
|
small
|
||||||
|
class="mr-2"
|
||||||
|
@click="
|
||||||
|
create = false;
|
||||||
|
actions.setWorking(item);
|
||||||
|
domUnitDialog.open();
|
||||||
|
"
|
||||||
|
></BaseButton>
|
||||||
|
<BaseDialog :title="$t('general.confirm')" color="error" @confirm="actions.deleteOne(item.id)">
|
||||||
|
<template #activator="{ open }">
|
||||||
|
<BaseButton delete small @click="open"></BaseButton>
|
||||||
|
</template>
|
||||||
|
<v-card-text>
|
||||||
|
{{ $t("general.confirm-delete-generic") }}
|
||||||
|
</v-card-text>
|
||||||
|
</BaseDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, reactive, toRefs, ref, computed } from "@nuxtjs/composition-api";
|
||||||
|
import { useUnits } from "~/composables/use-recipe-units";
|
||||||
|
import { validators } from "~/composables/use-validators";
|
||||||
|
export default defineComponent({
|
||||||
|
layout: "admin",
|
||||||
|
setup() {
|
||||||
|
const { units, actions, workingUnitData, validForm } = useUnits();
|
||||||
|
|
||||||
|
const domCreateUnitForm = ref(null);
|
||||||
|
const domUnitDialog = ref(null);
|
||||||
|
|
||||||
|
const dialog = computed(() => {
|
||||||
|
if (state.create) {
|
||||||
|
return {
|
||||||
|
title: "Create Unit",
|
||||||
|
text: "Create",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
title: "Edit Unit",
|
||||||
|
text: "Update",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
headers: [
|
||||||
|
{ text: "Id", value: "id" },
|
||||||
|
{ text: "Name", value: "name" },
|
||||||
|
{ text: "Abbreviation", value: "abbreviation" },
|
||||||
|
{ text: "Description", value: "description" },
|
||||||
|
{ text: "", value: "actions", sortable: false },
|
||||||
|
],
|
||||||
|
filter: false,
|
||||||
|
create: true,
|
||||||
|
search: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(state),
|
||||||
|
actions,
|
||||||
|
dialog,
|
||||||
|
domCreateUnitForm,
|
||||||
|
domUnitDialog,
|
||||||
|
units,
|
||||||
|
validators,
|
||||||
|
validForm,
|
||||||
|
workingUnitData,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
@ -92,6 +92,8 @@ import {
|
|||||||
mdiMinus,
|
mdiMinus,
|
||||||
mdiWindowClose,
|
mdiWindowClose,
|
||||||
mdiFolderZipOutline,
|
mdiFolderZipOutline,
|
||||||
|
mdiFoodApple,
|
||||||
|
mdiBeakerOutline,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
@ -99,6 +101,8 @@ const icons = {
|
|||||||
primary: mdiSilverwareVariant,
|
primary: mdiSilverwareVariant,
|
||||||
|
|
||||||
// General
|
// General
|
||||||
|
foods: mdiFoodApple,
|
||||||
|
units: mdiBeakerOutline,
|
||||||
alert: mdiAlert,
|
alert: mdiAlert,
|
||||||
alertCircle: mdiAlertCircle,
|
alertCircle: mdiAlertCircle,
|
||||||
api: mdiApi,
|
api: mdiApi,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user