Basic ingredient editor

This commit is contained in:
vabene1111
2025-01-02 14:56:18 +01:00
parent ac2d78f7a5
commit 741c05a519
37 changed files with 355 additions and 22 deletions

View File

@@ -27,11 +27,13 @@ import ApiSettings from "@/components/settings/ApiSettings.vue";
import ModelListPage from "@/pages/ModelListPage.vue";
import ModelEditPage from "@/pages/ModelEditPage.vue";
import RecipeImportPage from "@/pages/RecipeImportPage.vue";
import IngredientEditorPage from "@/pages/IngredientEditorPage.vue";
const routes = [
{path: '/', component: StartPage, name: 'view_home'},
{path: '/test', component: TestPage, name: 'view_test'},
{path: '/settings', component: SettingsPage, name: 'view_settings', redirect: '/settings/account',
{
path: '/settings', component: SettingsPage, name: 'view_settings', redirect: '/settings/account',
children: [
{path: 'account', component: AccountSettings, name: 'view_settings_account'},
{path: 'cosmetic', component: CosmeticSettings, name: 'view_settings_cosmetic'},
@@ -41,7 +43,8 @@ const routes = [
{path: 'space-members', component: SpaceMemberSettings, name: 'view_settings_space_member'},
{path: 'user-space', component: UserSpaceSettings, name: 'view_settings_user_space'},
{path: 'api', component: ApiSettings, name: 'view_settings_api'},
]},
]
},
//{path: '/settings/:page', component: SettingsPage, name: 'view_settings_page', props: true},
{path: '/search', component: SearchPage, name: 'view_search'},
{path: '/shopping', component: ShoppingListPage, name: 'view_shopping'},
@@ -51,9 +54,10 @@ const routes = [
{path: '/recipe/:id', component: RecipeViewPage, name: 'view_recipe', props: true},
{path: '/recipe/edit/:recipe_id', component: RecipeEditPage, name: 'edit_recipe', props: true},
{path: '/list/:model?', component: ModelListPage, props: true, name: 'ModelListPage'},
{path: '/edit/:model/:id?', component: ModelEditPage, props: true, name: 'ModelEditPage'},
{path: '/list/:model?', component: ModelListPage, props: true, name: 'ModelListPage'},
{path: '/edit/:model/:id?', component: ModelEditPage, props: true, name: 'ModelEditPage'},
{path: '/ingredient-editor', component: IngredientEditorPage, name: 'IngredientEditorPage'},
]
const router = createRouter({

View File

@@ -1,13 +1,14 @@
<template>
<!-- TODO label is not showing for some reason, for now in placeholder -->
<!-- TODO support density prop -->
<v-input :hint="props.hint" persistent-hint :label="props.label" class="" >
<v-input :hint="props.hint" persistent-hint :label="props.label" >
<!-- TODO resolve-on-load false for now, race condition with model class, make prop once better solution is found -->
<Multiselect
:ref="`ref_${props.id}`"
class="material-multiselect"
class="material-multiselect "
:class="{'model-select--density-compact': props.density == 'compact', 'model-select--density-comfortable': props.density == 'comfortable', 'model-select--density-default': props.density == ''}"
:resolve-on-load="props.searchOnLoad"
v-model="model"
:options="search"
@@ -53,7 +54,7 @@ const emit = defineEmits(['update:modelValue', 'create'])
const props = defineProps({
model: {type: String as PropType<EditorSupportedModels>, required: true},
id: {type: String, required: false, default: Math.floor(Math.random()*10000).toString()},
id: {type: String, required: false, default: Math.floor(Math.random() * 10000).toString()},
limit: {type: Number, default: 25},
@@ -71,6 +72,7 @@ const props = defineProps({
label: {type: String, default: ''},
hint: {type: String, default: ''},
density: {type: String as PropType<''|'compact'|'comfortable'>, default: ''},
searchOnLoad: {type: Boolean, default: false},
})
@@ -79,7 +81,7 @@ const props = defineProps({
* check if model has a non-standard value attribute defined, if not use "id" as the value attribute
*/
const itemValue = computed(() => {
if(modelClass.value.model.itemValue){
if (modelClass.value.model.itemValue) {
return modelClass.value.model.itemValue
}
return 'id'
@@ -89,7 +91,7 @@ const itemValue = computed(() => {
* check if model has a non-standard label attribute defined, if not use "name" as the value attribute
*/
const itemLabel = computed(() => {
if(modelClass.value.model.itemLabel){
if (modelClass.value.model.itemLabel) {
return modelClass.value.model.itemLabel
}
return 'name'
@@ -135,7 +137,7 @@ function search(query: string) {
* @param select$ reference to multiselect instance
*/
async function createObject(object: any, select$: Multiselect) {
return await modelClass.value.create({name: object[itemLabel.value]}).then((createdObj : any) => {
return await modelClass.value.create({name: object[itemLabel.value]}).then((createdObj: any) => {
useMessageStore().addMessage(MessageType.SUCCESS, 'Created', 5000, createdObj)
emit('create', object)
return createdObj
@@ -148,17 +150,29 @@ async function createObject(object: any, select$: Multiselect) {
</script>
<style src="@vueform/multiselect/themes/default.css"></style>
<style>
<style scoped>
.material-multiselect {
--ms-line-height: 2.3;
--ms-bg: rgba(235, 235, 235, 0.3);
--ms-bg: rgba(210, 210, 210, 0.1);
--ms-border-color: 0;
--ms-border-color-active: 0;
border-bottom: inset 1px #323232;
border-bottom: inset 1px rgba(50, 50, 50, 0.8);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.model-select--density-compact {
--ms-line-height: 1.3;
}
.model-select--density-comfortable {
--ms-line-height: 1.8;
}
.model-select--density-default {
--ms-line-height: 2.3;
}
.multiselect-tag {
background-color: #b98766 !important;
}

View File

@@ -178,7 +178,6 @@ import {useDisplay} from "vuetify";
import {VueDraggable} from "vue-draggable-plus";
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
import IngredientString from "@/components/display/IngredientString.vue";
import numberInput from "../../../../vue/src/components/Modals/NumberInput.vue";
const emit = defineEmits(['delete'])

View File

@@ -140,6 +140,7 @@
"Imported_From": "",
"Importer_Help": "",
"Information": "",
"Ingredient": "",
"Ingredient Editor": "",
"Ingredient Overview": "",
"IngredientInShopping": "",

View File

@@ -137,6 +137,7 @@
"Imported_From": "Внесено от",
"Importer_Help": "Повече информация и помощ за този вносител:",
"Information": "Информация",
"Ingredient": "",
"Ingredient Editor": "Редактор на съставки",
"IngredientInShopping": "Тази съставка е във вашия списък за пазаруване.",
"Ingredients": "Съставки",

View File

@@ -183,6 +183,7 @@
"Imported_From": "",
"Importer_Help": "",
"Information": "",
"Ingredient": "",
"Ingredient Editor": "Editor d'ingredients",
"Ingredient Overview": "",
"IngredientInShopping": "",

View File

@@ -183,6 +183,7 @@
"Imported_From": "Importováno z",
"Importer_Help": "Nápověda k importu z této aplikace:",
"Information": "Informace",
"Ingredient": "",
"Ingredient Editor": "Editace ingrediencí",
"Ingredient Overview": "Přehled ingrediencí",
"IngredientInShopping": "Tato ingredience je na vašem nákupním seznamu.",

View File

@@ -171,6 +171,7 @@
"Imported_From": "Importeret fra",
"Importer_Help": "Mere information og hjælp til denne importer:",
"Information": "Information",
"Ingredient": "",
"Ingredient Editor": "Ingrediens redigeringsværktøj",
"Ingredient Overview": "Ingrediensoversigt",
"IngredientInShopping": "Denne ingrediens er i din indkøbsliste.",

View File

@@ -185,6 +185,7 @@
"Imported_From": "Importiert aus",
"Importer_Help": "Zusätzliche Informationen und Hilfe zu diesem Importer:",
"Information": "Information",
"Ingredient": "Zutat",
"Ingredient Editor": "Zutateneditor",
"Ingredient Overview": "Zutatenübersicht",
"IngredientInShopping": "Diese Zutat befindet sich auf Ihrer Einkaufsliste.",

View File

@@ -166,6 +166,7 @@
"Imported_From": "Πηγή",
"Importer_Help": "Περισσότερες πληροφορίες και βοήθεια για αυτό το πρόγραμμα εισαγωγής:",
"Information": "Πληροφορίες",
"Ingredient": "",
"Ingredient Editor": "Επεξεργαστής συστατικών",
"Ingredient Overview": "Σύνοψη υλικών",
"IngredientInShopping": "Αυτό το υλικό είναι στη λίστα αγορών.",

View File

@@ -184,6 +184,7 @@
"Imported_From": "Imported from",
"Importer_Help": "More information and help on this importer:",
"Information": "Information",
"Ingredient": "Ingredient",
"Ingredient Editor": "Ingredient Editor",
"Ingredient Overview": "Ingredient Overview",
"IngredientInShopping": "This ingredient is in your shopping list.",

View File

@@ -184,6 +184,7 @@
"Imported_From": "Importado de",
"Importer_Help": "Más información y ayuda con este importador:",
"Information": "Información",
"Ingredient": "",
"Ingredient Editor": "Ingredientes",
"Ingredient Overview": "Vistazo de Ingredientes",
"IngredientInShopping": "Este ingrediente ya esta en la lista de la compra.",

View File

@@ -100,6 +100,7 @@
"Import": "Tuo",
"Import_finished": "Tuonti valmistui",
"Information": "Tiedot",
"Ingredient": "",
"Ingredients": "Ainesosat",
"Instructions": "Ohjeet",
"InstructionsEditHelp": "",

View File

@@ -183,6 +183,7 @@
"Imported_From": "Importé depuis",
"Importer_Help": "Plus d'information et d'aide sur cet importateur :",
"Information": "Information",
"Ingredient": "",
"Ingredient Editor": "Éditeur dingrédients",
"Ingredient Overview": "Aperçu des ingrédients",
"IngredientInShopping": "Cet ingrédient est dans votre liste de courses.",

View File

@@ -184,6 +184,7 @@
"Imported_From": "יובא מ",
"Importer_Help": "עוד מידע ועזרה על כלי ייבוא זה:",
"Information": "מידע",
"Ingredient": "",
"Ingredient Editor": "עורך המרכיב",
"Ingredient Overview": "סקירת רכיב",
"IngredientInShopping": "רכיב זה ברשימת הקניות.",

View File

@@ -167,6 +167,7 @@
"Imported_From": "Importálva",
"Importer_Help": "",
"Information": "Információ",
"Ingredient": "",
"Ingredient Editor": "Hozzávalók szerkesztője",
"Ingredient Overview": "Hozzávalók áttekintése",
"IngredientInShopping": "Ez a hozzávaló szerepel a bevásárlólistán.",

View File

@@ -75,6 +75,7 @@
"Import": "Ներմուծել",
"Import_finished": "Ներմուծումն ավարտված է",
"Information": "Տեղեկություն",
"Ingredient": "",
"Ingredients": "",
"InstructionsEditHelp": "",
"Invite_Link": "",

View File

@@ -154,6 +154,7 @@
"Imported_From": "",
"Importer_Help": "",
"Information": "Informasi",
"Ingredient": "",
"Ingredient Editor": "Editor Bahan",
"Ingredient Overview": "",
"IngredientInShopping": "",

View File

@@ -183,6 +183,7 @@
"Imported_From": "",
"Importer_Help": "",
"Information": "",
"Ingredient": "",
"Ingredient Editor": "",
"Ingredient Overview": "",
"IngredientInShopping": "",

View File

@@ -159,6 +159,7 @@
"Imported_From": "Importato da",
"Importer_Help": "Per altre informazioni e aiuto su questo importer:",
"Information": "Informazioni",
"Ingredient": "",
"Ingredient Editor": "Editor Ingredienti",
"Ingredient Overview": "Panoramica Ingredienti",
"IngredientInShopping": "Questo ingrediente è nella tua lista della spesa.",

View File

@@ -169,6 +169,7 @@
"Imported_From": "",
"Importer_Help": "",
"Information": "",
"Ingredient": "",
"Ingredient Editor": "Ingredientų redaktorius",
"Ingredient Overview": "",
"IngredientInShopping": "",

View File

@@ -164,6 +164,7 @@
"Imported_From": "",
"Importer_Help": "",
"Information": "Informasjon",
"Ingredient": "",
"Ingredient Editor": "Ingrediens Behandler",
"Ingredient Overview": "",
"IngredientInShopping": "Denne ingrediensen er i handlekurven din.",

View File

@@ -168,6 +168,7 @@
"Imported_From": "Geïmporteerd van",
"Importer_Help": "Meer informatie en hulp over de importtool:",
"Information": "Informatie",
"Ingredient": "",
"Ingredient Editor": "Ingrediënten editor",
"Ingredient Overview": "Ingrediëntenlijst",
"IngredientInShopping": "Dit ingrediënt staat op je boodschappenlijst.",

View File

@@ -185,6 +185,7 @@
"Imported_From": "Zaimportowane z",
"Importer_Help": "Więcej informacji i pomoc na temat tego importera:",
"Information": "Informacja",
"Ingredient": "",
"Ingredient Editor": "Edytor składników",
"Ingredient Overview": "Przegląd składników",
"IngredientInShopping": "Ten składnik znajduje się na Twojej liście zakupów.",

View File

@@ -132,6 +132,7 @@
"Import": "Importar",
"Import_finished": "Importação terminada",
"Information": "Informação",
"Ingredient": "",
"Ingredient Editor": "Editor de Ingredientes",
"IngredientInShopping": "Este ingrediente está na sua lista de compras.",
"Ingredients": "Ingredientes",

View File

@@ -178,6 +178,7 @@
"Imported_From": "Importado de",
"Importer_Help": "Mais informações neste importador:",
"Information": "Informação",
"Ingredient": "",
"Ingredient Editor": "Editor de Ingrediente",
"Ingredient Overview": "Ingredientes - Visão Geral",
"IngredientInShopping": "Este ingrediente está na sua lista de compras.",

View File

@@ -162,6 +162,7 @@
"Imported_From": "Importat din",
"Importer_Help": "Mai multe informații și ajutor cu privire la acest importator:",
"Information": "Informație",
"Ingredient": "",
"Ingredient Editor": "Editor de ingrediente",
"Ingredient Overview": "Prezentare generală a ingredientelor",
"IngredientInShopping": "Acest ingredient se află în lista de cumpărături.",

View File

@@ -124,6 +124,7 @@
"Imported": "Импортировано",
"Imported_From": "Импортировано из",
"Information": "Информация",
"Ingredient": "",
"Ingredient Editor": "Редактор ингредиентов",
"IngredientInShopping": "Этот ингредиент в вашем списке покупок.",
"Ingredients": "Ингредиенты",

View File

@@ -120,6 +120,7 @@
"Import": "Uvozi",
"Import_finished": "Uvoz je končan",
"Information": "Informacija",
"Ingredient": "",
"Ingredient Editor": "Urejevalnik Sestavin",
"IngredientInShopping": "Ta sestavina je v tvojem nakupovalnem listku.",
"Ingredients": "Sestavine",

View File

@@ -185,6 +185,7 @@
"Imported_From": "Importerad från",
"Importer_Help": "Mer information och hjälp om denna import:",
"Information": "Information",
"Ingredient": "",
"Ingredient Editor": "Ingrediensredigerare",
"Ingredient Overview": "Ingrediensöversikt",
"IngredientInShopping": "Denna ingrediens finns i din inköpslista.",

View File

@@ -184,6 +184,7 @@
"Imported_From": "İçe Aktarıldığı Yer",
"Importer_Help": "Bu içe aktarıcı hakkında daha fazla bilgi ve yardım:",
"Information": "Bilgi",
"Ingredient": "",
"Ingredient Editor": "Malzeme Düzenleyici",
"Ingredient Overview": "Malzeme Genel Bakış",
"IngredientInShopping": "Bu malzeme alışveriş listenizde.",

View File

@@ -146,6 +146,7 @@
"Imported_From": "",
"Importer_Help": "",
"Information": "Інформація",
"Ingredient": "",
"Ingredient Editor": "Редактор Інгредієнтів",
"IngredientInShopping": "Цей інгредієнт є в вашому списку покупок.",
"Ingredients": "Інгредієнти",

View File

@@ -180,6 +180,7 @@
"Imported_From": "导入",
"Importer_Help": "有关此进口商的更多信息和帮助:",
"Information": "更多信息",
"Ingredient": "",
"Ingredient Editor": "食材编辑器",
"Ingredient Overview": "食材概述",
"IngredientInShopping": "此食材已在购物清单中。",

View File

@@ -60,6 +60,7 @@
"Import": "",
"Import_finished": "匯入完成",
"Information": "",
"Ingredient": "",
"Ingredients": "",
"InstructionsEditHelp": "",
"Invite_Link": "",

View File

@@ -0,0 +1,246 @@
<template>
<v-container>
<v-card :loading="filtersLoading">
<v-card-title>{{ $t('Ingredient Editor') }}</v-card-title>
<v-card-text>
<closable-help-alert
class="mb-2"
text="With the ingredient editor you can edit all Ingredients that use a certain Food and/or Unit at once. This can be used to easily correct errors or change multiple recipes at once."></closable-help-alert>
<v-row>
<v-col>
<model-select model="Food" v-model="selectedFood" @update:modelValue="refreshPage()" append-to-body></model-select>
</v-col>
<v-col>
<model-select model="Unit" v-model="selectedUnit" @update:modelValue="refreshPage()" append-to-body></model-select>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-card class="mt-2">
<v-data-table-server
@update:options="loadItems"
:items="items"
:items-length="tableItemCount"
:items-per-page="tablePageSize"
:headers="tableHeaders"
:expanded="items.flatMap((i:Ingredient) => i.id)"
:page="tablePage"
:loading="ingredientsLoading"
>
<template v-slot:header.action="{ column }">
<v-btn size="small" color="save" @click="updateAllIngredients()">
<v-icon icon="$save"></v-icon>
</v-btn>
</template>
<template v-slot:expanded-row="{ columns, item }">
<tr>
<td :colspan="columns.length">
<v-btn variant="outlined" color="secondary" target="_blank" :to="{name: 'view_recipe', params: {id: r.id}}" v-for="r in item.usedInRecipes">
{{ r.name }}
</v-btn>
</td>
</tr>
</template>
<template v-slot:item.amount="{ item }">
<v-number-input :label="$t('Amount')" v-model="item.amount" inset control-variant="stacked" hide-details :min="0" density="compact"
@update:modelValue="item.changed = true"></v-number-input>
</template>
<template v-slot:item.unit="{ item }">
<model-select model="Unit" v-model="item.unit" :label="$t('Unit')" density="compact" hide-details allow-create append-to-body
@update:modelValue="item.changed = true"></model-select>
</template>
<template v-slot:item.food="{ item }">
<model-select model="Food" v-model="item.food" :label="$t('Food')" density="compact" hide-details allow-create append-to-body
@update:modelValue="item.changed = true"></model-select>
</template>
<template v-slot:item.note="{ item }">
<v-text-field v-model="item.note" :label="$t('Note')" density="compact" hide-details @update:modelValue="item.changed = true"></v-text-field>
</template>
<template v-slot:item.action="{ item }">
<v-btn-group density="comfortable">
<v-btn size="small" color="save" :loading="item.loading" @click="updateIngredient(item)" :disabled="!item.changed">
<v-icon icon="$save" ></v-icon>
</v-btn>
<v-btn size="small" color="delete" :loading="item.loading">
<v-icon icon="$delete"></v-icon>
<delete-confirm-dialog :model-name="$t('Ingredient')" @delete="deleteIngredient(item)"></delete-confirm-dialog>
</v-btn>
</v-btn-group>
</template>
</v-data-table-server>
</v-card>
</v-container>
</template>
<script setup lang="ts">
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import ClosableHelpAlert from "@/components/display/ClosableHelpAlert.vue";
import {ApiApi, ApiIngredientListRequest, Food, Ingredient, Unit} from "@/openapi";
import {onMounted, ref} from "vue";
import {useI18n} from "vue-i18n";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import {useUrlSearchParams} from "@vueuse/core";
import {VNumberInput} from 'vuetify/labs/VNumberInput'
import DeleteConfirmDialog from "@/components/dialogs/DeleteConfirmDialog.vue";
const {t} = useI18n()
const params = useUrlSearchParams('history', {})
type EditorIngredient = Ingredient & { changed: boolean, loading: boolean }
const items = ref([] as EditorIngredient[])
const tableHeaders = [
{title: t('Amount'), key: 'amount', minWidth: '120px', cellProps: {class: 'pr-0'}},
{title: t('Unit'), key: 'unit', minWidth: '120px', cellProps: {class: 'pr-0'}},
{title: t('Food'), key: 'food', minWidth: '120px', cellProps: {class: 'pr-0'}},
{title: t('Note'), key: 'note', minWidth: '120px', cellProps: {class: 'pr-0'}},
{key: 'action', width: '1%', noBreak: true, align: 'end'},
]
const tablePage = ref(1)
const tablePageSize = ref(25)
const tableItemCount = ref(0)
const ingredientsLoading = ref(false)
const filtersLoading = ref(true)
const selectedFood = ref<null | Food>(null)
const selectedUnit = ref<null | Unit>(null)
const deleteConfirmDialog = ref(false)
const deleteConfirmIngredient = ref({} as EditorIngredient)
onMounted(() => {
getAndLoadParameters()
})
/**
* update all changed ingredients
*/
function updateAllIngredients() {
items.value.forEach((item) => {
if (item.changed) {
updateIngredient(item)
}
})
}
/**
* update a single ingredient in the database
* @param ingredient
*/
function updateIngredient(ingredient: EditorIngredient) {
let api = new ApiApi()
ingredient.loading = true
api.apiIngredientUpdate({id: ingredient.id!, ingredient: ingredient}).then(r => {
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}).finally(() => {
ingredient.loading = false
ingredient.changed = false
})
}
/**
* delete the given ingredient form the database and local client data
* @param ingredient
*/
function deleteIngredient(ingredient: EditorIngredient) {
let api = new ApiApi()
ingredient.loading = true
api.apiIngredientDestroy({id: ingredient.id!}).then(r => {
items.value = items.value.filter(i => i.id != ingredient.id)
}).catch(err => {
useMessageStore().addError(ErrorMessageType.DELETE_ERROR, err)
}).finally(() => {
ingredient.loading = false
})
}
/**
* use URL parameters to retrieve associated food or unit, after that load table data
*/
function getAndLoadParameters() {
let api = new ApiApi()
let promises: Promise<any>[] = []
filtersLoading.value = true
if (params.food_id && !Number.isNaN(params.food_id)) {
promises.push(api.apiFoodRetrieve({id: Number(params.food_id)}).then(r => {
selectedFood.value = r
}))
}
if (params.unit_id && !Number.isNaN(params.unit_id)) {
promises.push(api.apiUnitRetrieve({id: Number(params.unit_id)}).then(r => {
selectedUnit.value = r
}))
}
Promise.allSettled(promises).then(() => {
filtersLoading.value = false
if (params.food_id || params.unit_id) {
refreshPage()
}
})
}
/**
* manually trigger item load
*/
function refreshPage() {
loadItems({page: tablePage.value, itemsPerPage: tablePageSize.value})
}
/**
* load items from server
* @param page
* @param itemsPerPage
* @param search
* @param sortBy
* @param groupBy
*/
function loadItems({page, itemsPerPage, search, sortBy, groupBy}) {
// never load unfiltered, only load if at least one filter is set
if (!selectedFood.value && !selectedUnit.value) {
return
}
let api = new ApiApi()
ingredientsLoading.value = true
let requestParameters: ApiIngredientListRequest = {page: page, pageSize: itemsPerPage}
if (selectedFood.value) {
requestParameters.food = selectedFood.value.id!
}
if (selectedUnit.value) {
requestParameters.unit = selectedUnit.value.id!
}
api.apiIngredientList(requestParameters).then(r => {
items.value = r.results
tableItemCount.value = r.count
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}).finally(() => {
ingredientsLoading.value = false
})
}
</script>
<style scoped>
</style>

View File

@@ -51,10 +51,16 @@
<v-list-item prepend-icon="$edit" :to="{name: 'ModelEditPage', params: {model: model, id: item.id}}">
{{ $t('Edit') }}
</v-list-item>
<v-list-item prepend-icon="fa-solid fa-arrows-to-dot" link>
<v-list-item prepend-icon="fa-solid fa-arrows-to-dot" v-if="genericModel.model.isMerge" link>
{{ $t('Merge') }}
<model-merge-dialog :model="model" :source="item" @change="loadItems({page: tablePage, itemsPerPage: useUserPreferenceStore().deviceSettings.general_tableItemsPerPage})"></model-merge-dialog>
</v-list-item>
<v-list-item prepend-icon="fa-solid fa-table-list" :to="{name: 'IngredientEditorPage', query: {food_id: item.id}}" v-if="genericModel.model.name == 'Food'">
{{ $t('Ingredient Editor') }}
</v-list-item>
<v-list-item prepend-icon="fa-solid fa-table-list" :to="{name: 'IngredientEditorPage', query: {unit_id: item.id}}" v-if="genericModel.model.name == 'Unit'">
{{ $t('Ingredient Editor') }}
</v-list-item>
</v-list>
</v-menu>
</v-btn>
@@ -105,12 +111,6 @@ const itemsPerPageOptions = [
{value: 50, title: '50'},
]
const tableHeaders: VDataTableProps['headers'] = [
{title: t('Name'), key: 'name'},
{title: t('Category'), key: 'supermarketCategory.name'},
{title: t('Actions'), key: 'action', align: 'end'},
]
const tablePage = ref(1)
const tablePageInitialized = ref(false) // TODO workaround until vuetify bug is fixed
@@ -175,6 +175,10 @@ function loadItems({page, itemsPerPage, search, sortBy, groupBy}) {
})
}
/**
* change models and reset page/scroll
* @param m
*/
function changeModel(m: Model) {
tablePage.value = 1
router.push({name: 'ModelListPage', params: {model: m.name.toLowerCase()}})

View File

@@ -12,6 +12,40 @@
<model-edit-dialog model="MealPlan" v-model="dialog" :item="defaultItem" :activator="activator"></model-edit-dialog>
</v-btn>
<v-row class="mt-5">
<v-col>
<v-text-field density="compact"></v-text-field>
</v-col>
<v-col>
<model-select model="Food" density="compact"></model-select>
</v-col>
</v-row>
<v-row class="mt-5">
<v-col>
<v-text-field density="comfortable"></v-text-field>
</v-col>
<v-col>
<model-select model="Food" density="comfortable"></model-select>
</v-col>
</v-row>
<v-row class="mt-5">
<v-col>
<v-text-field></v-text-field>
</v-col>
<v-col>
<model-select model="Food"></model-select>
</v-col>
</v-row>
</template>
<script setup lang="ts">
@@ -21,6 +55,7 @@ import {ApiApi, MealPlan} from "@/openapi";
import {ref, useTemplateRef} from "vue";
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
import {DateTime} from "luxon";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
const image = ref(File)
const response = ref('')