shopping from meal plan edit

This commit is contained in:
vabene1111
2024-12-28 23:26:22 +01:00
parent bf4fc9a7aa
commit c1bfa563a3
45 changed files with 299 additions and 160 deletions

View File

@@ -5,7 +5,7 @@
</template>
<p>
{{ props.text}}
<v-btn color="success" class="float-right" v-if="props.actionText != ''" @click="emit('click')">{{ actionText}}</v-btn>
<v-btn color="success" class="float-right" v-if="props.actionText" @click="emit('click')">{{ actionText}}</v-btn>
</p>
</v-alert>
</template>

View File

@@ -34,7 +34,7 @@
</v-card>
<model-edit-dialog model="MealPlan" v-model="newPlanDialog" :itemDefaults="newPlanDialogDefaultItem"
<model-edit-dialog model="MealPlan" v-model="newPlanDialog" :itemDefaults="newPlanDialogDefaultItem" :close-after-create="false"
@create="(arg: any) => useMealPlanStore().plans.set(arg.id, arg)"></model-edit-dialog>
</v-col>
</v-row>

View File

@@ -154,7 +154,7 @@ const amounts = computed((): Map<number, ShoppingLineAmount> => {
* compute the second (info) row of the line item based on the entries and the device settings
*/
const infoRow = computed(() => {
if(props.hideInfoRow){
if (props.hideInfoRow) {
return ''
}
@@ -171,14 +171,16 @@ const infoRow = computed(() => {
authors.push(e.createdBy.displayName)
}
if (e.recipeMealplan !== null) {
let recipe_name = e.recipeMealplan.recipeName
if (recipes.indexOf(recipe_name) === -1) {
recipes.push(recipe_name.substring(0, 14) + (recipe_name.length > 14 ? '..' : ''))
if (e.listRecipe != null) {
if (e.listRecipeData.recipe != null) {
let recipe_name = e.listRecipeData.recipeData.name
if (recipes.indexOf(recipe_name) === -1) {
recipes.push(recipe_name.substring(0, 14) + (recipe_name.length > 14 ? '..' : ''))
}
}
if ('mealplan_from_date' in e.recipeMealplan) {
let meal_plan_entry = (e?.recipeMealplan?.mealplanType || '') + ' (' + DateTime.fromJSDate(e.recipeMealplan.mealplanFromDate).toLocaleString(DateTime.DATETIME_SHORT) + ')'
if (e.listRecipeData.mealplan != null) {
let meal_plan_entry = (e.listRecipeData.mealPlanData.mealType.name.substring(0, 8) || '') + ' (' + DateTime.fromJSDate(e.listRecipeData.mealPlanData.fromDate).toLocaleString(DateTime.DATE_SHORT) + ')'
if (meal_pans.indexOf(meal_plan_entry) === -1) {
meal_pans.push(meal_plan_entry)
}

View File

@@ -83,16 +83,7 @@
</template>
</v-alert>
<v-text-field :label="$t('Shopping_input_placeholder')" density="compact" @keyup.enter="addIngredient()" v-model="ingredientInput" hide-details>
<template #append>
<v-btn
density="comfortable"
@click="addIngredient()"
:icon="ingredientInputIcon"
color="create"
></v-btn>
</template>
</v-text-field>
<shopping-list-entry-input></shopping-list-entry-input>
<v-list class="mt-3" density="compact" v-if="!useShoppingStore().initialized">
<v-skeleton-loader type="list-item"></v-skeleton-loader>
@@ -168,7 +159,7 @@
<v-row>
<v-col>
<v-card>
<v-card-title>{{ $t('Recipes') }}</v-card-title>
<v-card-title>{{ $t('Recipes') }} / {{ $t('Meal_Plan') }}</v-card-title>
<v-card-text>
<v-list>
<v-list-item v-for="r in useShoppingStore().getAssociatedRecipes()">
@@ -179,9 +170,14 @@
@confirm="(servings: number) => {updateRecipeServings(r, servings)}"></number-scaler-dialog>
</v-btn>
</template>
<span class="ms-2">
{{ r.recipeName }}
</span>
<div class="ms-2">
<p v-if="r.recipe">{{ r.recipeData.name }} <br/></p>
<p v-if="r.mealplan">
{{ r.mealPlanData.mealType.name }} - {{ DateTime.fromJSDate(r.mealPlanData.fromDate).toLocaleString(DateTime.DATE_FULL) }}
</p>
</div>
<template #append>
<v-btn icon color="delete">
<v-icon icon="$delete"></v-icon>
@@ -230,14 +226,13 @@ import {useI18n} from "vue-i18n";
import NumberScalerDialog from "@/components/inputs/NumberScalerDialog.vue";
import SupermarketEditor from "@/components/model_editors/SupermarketEditor.vue";
import DeleteConfirmDialog from "@/components/dialogs/DeleteConfirmDialog.vue";
import ShoppingListEntryInput from "@/components/inputs/ShoppingListEntryInput.vue";
import {DateTime} from "luxon";
const {t} = useI18n()
const currentTab = ref("shopping")
const ingredientInput = ref('')
const ingredientInputIcon = ref('fa-solid fa-plus')
const shoppingLineItemDialog = ref(false)
const shoppingLineItemDialogFood = ref({} as IShoppingListFood)
@@ -273,29 +268,6 @@ onMounted(() => {
}
})
/**
* add new ingredient from ingredient text input
*/
function addIngredient() {
const api = new ApiApi()
api.apiIngredientFromStringCreate({ingredientString: {text: ingredientInput.value} as IngredientString}).then(r => {
useShoppingStore().createObject({
amount: Math.max(r.amount, 1),
unit: r.unit,
food: r.food,
} as ShoppingListEntry, true)
ingredientInput.value = ''
ingredientInputIcon.value = 'fa-solid fa-check'
setTimeout(() => {
ingredientInputIcon.value = 'fa-solid fa-plus'
}, 1000)
}).catch(err => {
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
})
}
/**
* determines if a category as entries that should be visible
* @param category

View File

@@ -0,0 +1,73 @@
<template>
<v-text-field :label="$t('Shopping_input_placeholder')" density="compact" @keyup.enter="addIngredient()" v-model="ingredientInput" :loading="props.loading" hide-details>
<template #append>
<v-btn
density="comfortable"
@click="addIngredient()"
:icon="ingredientInputIcon"
color="create"
></v-btn>
</template>
</v-text-field>
</template>
<script setup lang="ts">
import {PropType, ref} from "vue";
import {ApiApi, IngredientString, MealPlan, ShoppingListEntry, ShoppingListRecipe} from "@/openapi";
import {useShoppingStore} from "@/stores/ShoppingStore";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
const props = defineProps({
shoppingListRecipe: {type: {} as PropType<ShoppingListRecipe>, required: false},
mealPlan: {type: {} as PropType<MealPlan>, required: false},
loading: {type: Boolean, required: false},
})
const ingredientInput = ref('')
const ingredientInputIcon = ref('fa-solid fa-plus')
const loading = ref(false)
/**
* add new ingredient from ingredient text input
*/
function addIngredient() {
const api = new ApiApi()
loading.value = true
api.apiIngredientFromStringCreate({ingredientString: {text: ingredientInput.value} as IngredientString}).then(r => {
let sle = {
amount: Math.max(r.amount, 1),
unit: r.unit,
food: r.food,
} as ShoppingListEntry
console.log('adding SLR ? ', props.mealPlan)
if (props.mealPlan) {
console.log('yes')
sle.mealplanId = props.mealPlan.id
}
useShoppingStore().createObject(sle, true).finally(() => {
loading.value = false
})
ingredientInput.value = ''
ingredientInputIcon.value = 'fa-solid fa-check'
setTimeout(() => {
ingredientInputIcon.value = 'fa-solid fa-plus'
}, 1000)
}).catch(err => {
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
loading.value = false
})
}
</script>
<style scoped>
</style>

View File

@@ -11,7 +11,7 @@
<v-tabs v-model="tab" :disabled="loading" grow>
<v-tab prepend-icon="$mealplan" value="plan">{{ $t('Meal_Plan') }}</v-tab>
<v-tab prepend-icon="$shopping" value="shopping">{{ $t('Shopping_list') }}</v-tab>
<v-tab prepend-icon="$shopping" value="shopping" :disabled="!isUpdate()">{{ $t('Shopping_list') }}</v-tab>
</v-tabs>
<v-card-text>
@@ -67,26 +67,23 @@
</v-tabs-window-item>
<v-tabs-window-item value="shopping">
<v-text-field :label="$t('Shopping_input_placeholder')" density="compact" @keyup.enter="addIngredient()" v-model="ingredientInput" hide-details>
<template #append>
<v-btn
density="comfortable"
@click="addIngredient()"
:icon="ingredientInputIcon"
color="create"
></v-btn>
</template>
</v-text-field>
<closable-help-alert class="mb-2" :text="$t('MealPlanShoppingHelp')"></closable-help-alert>
<v-row v-if="isUpdate()" dense style="max-height: 75vh" class="overflow-scroll">
<v-col>
<shopping-list-entry-input :loading="useShoppingStore().currentlyUpdating" :meal-plan="editingObj"></shopping-list-entry-input>
<v-list v-if="editingObj.id">
<shopping-line-item
v-for="slf in useShoppingStore().getMealPlanEntries(editingObj.id)"
:shopping-list-food="slf"
hide-info-row
></shopping-line-item>
</v-list>
</v-col>
</v-row>
<v-progress-linear class="mt-2" indeterminate v-if="useShoppingStore().currentlyUpdating"></v-progress-linear>
<v-list v-if="editingObj.id">
<shopping-line-item
v-for="slf in useShoppingStore().getMealPlanEntries(editingObj.id)"
:shopping-list-food="slf"
hide-info-row
></shopping-line-item>
</v-list>
</v-tabs-window-item>
</v-tabs-window>
@@ -99,7 +96,7 @@
<script setup lang="ts">
import {onMounted, PropType, ref} from "vue";
import {ApiApi, MealPlan, MealType, ShoppingListEntry} from "@/openapi";
import {ApiApi, MealPlan, MealType, ShoppingListRecipe} from "@/openapi";
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
import {DateTime} from "luxon";
@@ -111,8 +108,9 @@ import {VDateInput} from "vuetify/labs/VDateInput";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import {ErrorMessageType, MessageType, useMessageStore} from "@/stores/MessageStore";
import ShoppingLineItem from "@/components/display/ShoppingLineItem.vue";
import {IShoppingListFood} from "@/types/Shopping";
import {useShoppingStore} from "@/stores/ShoppingStore";
import ShoppingListEntryInput from "@/components/inputs/ShoppingListEntryInput.vue";
import ClosableHelpAlert from "@/components/display/ClosableHelpAlert.vue";
const props = defineProps({
item: {type: {} as PropType<MealPlan>, required: false, default: null},
@@ -125,9 +123,10 @@ const emit = defineEmits(['create', 'save', 'delete', 'close'])
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, applyItemDefaults, loading, editingObj, modelClass} = useModelEditorFunctions<MealPlan>('MealPlan', emit)
// object specific data (for selects/display)
const tab = ref('shopping')
const tab = ref('plan')
const dateRangeValue = ref([] as Date[])
const shoppingListRecipe = ref<ShoppingListRecipe | undefined>(undefined)
onMounted(() => {
const api = new ApiApi()
@@ -163,6 +162,12 @@ onMounted(() => {
}, existingItemFunction: () => {
initializeDateRange()
useShoppingStore().refreshFromAPI(editingObj.value.id!)
api.apiShoppingListRecipeList({mealplan: editingObj.value.id!}).then(r => {
if (r.results.length > 0) {
shoppingListRecipe.value = r.results[0]
}
})
}
},)
})
@@ -198,6 +203,24 @@ function initializeDateRange() {
}
}
/**
* manually create a shopping list recipe (without a recipe) that links manually created entries to the shopping list
*/
function createShoppingListRecipe() {
let api = new ApiApi()
let slr = {
mealplan: editingObj.value.id,
servings: editingObj.value.servings,
} as ShoppingListRecipe
api.apiShoppingListRecipeCreate({shoppingListRecipe: slr}).then(r => {
shoppingListRecipe.value = r
}).catch(err => {
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
})
}
</script>
<style scoped>

View File

@@ -4,6 +4,7 @@ import {EditorSupportedModels, GenericModel, getGenericModelFromString} from "@/
import {useI18n} from "vue-i18n";
import {ResponseError} from "@/openapi";
import {getNestedProperty} from "@/utils/utils";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
// TODO type emit parameter (https://mokkapps.de/vue-tips/emit-event-from-composable)
// TODO alternatively there seems to be a getContext method to get the calling context (good practice?)
@@ -119,7 +120,13 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
}
let name = ''
if (editingObj.value.id) {
if (useUserPreferenceStore().serverSettings.debug) {
name += '#' + editingObj.value.id
}
modelClass.value.model.toStringKeys.forEach(key => {
let value = getNestedProperty(editingObj.value, key)
name += ' ' + ((value != null) ? value : '')

View File

@@ -165,6 +165,7 @@
"Make_Ingredient": "",
"ManageSubscription": "",
"Manage_Books": "",
"MealPlanShoppingHelp": "",
"Meal_Plan": "",
"Meal_Plan_Days": "",
"Meal_Type": "",

View File

@@ -160,6 +160,7 @@
"Make_Ingredient": "Направете съставка",
"ManageSubscription": "",
"Manage_Books": "Управление на Книги",
"MealPlanShoppingHelp": "",
"Meal_Plan": "План на хранене",
"Meal_Plan_Days": "Бъдещи планове за хранене",
"Meal_Type": "Вид хранене",

View File

@@ -215,6 +215,7 @@
"ManageSubscription": "",
"Manage_Books": "Gestioneu els llibres",
"Manage_Emails": "",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Pla d'àpats",
"Meal_Plan_Days": "",
"Meal_Type": "",

View File

@@ -214,6 +214,7 @@
"ManageSubscription": "",
"Manage_Books": "Spravovat kuchařky",
"Manage_Emails": "Spravovat emaily",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Jídelníček",
"Meal_Plan_Days": "Budoucí jídelníčky",
"Meal_Type": "Druh jídla",

View File

@@ -201,6 +201,7 @@
"ManageSubscription": "",
"Manage_Books": "Administrer bøger",
"Manage_Emails": "Håndter Emails",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Madplan",
"Meal_Plan_Days": "Fremtidige madplaner",
"Meal_Type": "Måltidstype",

View File

@@ -217,6 +217,7 @@
"ManageSubscription": "Tarfi verwalten",
"Manage_Books": "Bücher verwalten",
"Manage_Emails": "E-Mails verwalten",
"MealPlanShoppingHelp": "Einträge auf der Einkaufsliste können zur besseren Sortierung zu einem Plan gehören. Wird ein Plan mit einem Rezept erstellt, können die Zutaten automatisch auf die Einkaufsliste gesetzt werden (Einstellung).",
"Meal_Plan": "Speiseplan",
"Meal_Plan_Days": "Zukünftige Essenspläne",
"Meal_Type": "Mahlzeit",

View File

@@ -196,6 +196,7 @@
"ManageSubscription": "",
"Manage_Books": "Διαχείριση βιβλίων",
"Manage_Emails": "Διαχείριση email",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Πρόγραμμα γευμάτων",
"Meal_Plan_Days": "Μελλοντικά προγράμματα γευμάτων",
"Meal_Type": "Είδος γεύματος",

View File

@@ -216,6 +216,7 @@
"ManageSubscription": "Manage subscription",
"Manage_Books": "Manage Books",
"Manage_Emails": "Manage Emails",
"MealPlanShoppingHelp": "Entries on you Shopping List can be related to a Mealplan to sort your list or update/delete them all at once. When creating a Mealplan with a Recipe Shopping List entries for that recipe can be created automatically (setting). ",
"Meal_Plan": "Meal Plan",
"Meal_Plan_Days": "Future meal plans",
"Meal_Type": "Meal type",

View File

@@ -216,6 +216,7 @@
"ManageSubscription": "",
"Manage_Books": "Manejar libros",
"Manage_Emails": "Administrar Correos",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Régimen de comida",
"Meal_Plan_Days": "Planes de comida a futuro",
"Meal_Type": "Tipo de comida",

View File

@@ -115,6 +115,7 @@
"Make_Ingredient": "Valmista Ainesosa",
"ManageSubscription": "",
"Manage_Books": "Hallinnoi kirjoja",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Ateriasuunnitelma",
"Meal_Plan_Days": "Tulevat ruokasuunnitelmat",
"Meal_Type": "Ateriatyyppi",

View File

@@ -215,6 +215,7 @@
"ManageSubscription": "",
"Manage_Books": "Gérer les livres",
"Manage_Emails": "Gérer les e-mails",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Menu de la semaine",
"Meal_Plan_Days": "Futurs menus",
"Meal_Type": "Type de repas",

View File

@@ -216,6 +216,7 @@
"ManageSubscription": "",
"Manage_Books": "נהל ספרים",
"Manage_Emails": "נהל כתובות דואר אלקטרוני",
"MealPlanShoppingHelp": "",
"Meal_Plan": "תוכנית ארוחה",
"Meal_Plan_Days": "תכנון אוכל עתידי",
"Meal_Type": "סוג אוכל",

View File

@@ -197,6 +197,7 @@
"ManageSubscription": "",
"Manage_Books": "Könyvek kezelése",
"Manage_Emails": "Levelezés kezelése",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Menüterv",
"Meal_Plan_Days": "Jövőbeni menütervek",
"Meal_Type": "Étkezés",

View File

@@ -84,6 +84,7 @@
"Logout": "",
"ManageSubscription": "",
"Manage_Books": "Կարգավորել Գրքերը",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Ճաշացուցակ",
"Merge": "Միացնել",
"Merge_Keyword": "Միացնել բանալի բառը",

View File

@@ -182,6 +182,7 @@
"ManageSubscription": "",
"Manage_Books": "Kelola Buku",
"Manage_Emails": "",
"MealPlanShoppingHelp": "",
"Meal_Plan": "rencana makan",
"Meal_Plan_Days": "",
"Meal_Type": "",

View File

@@ -215,6 +215,7 @@
"ManageSubscription": "",
"Manage_Books": "",
"Manage_Emails": "",
"MealPlanShoppingHelp": "",
"Meal_Plan": "",
"Meal_Plan_Days": "",
"Meal_Type": "",

View File

@@ -187,6 +187,7 @@
"ManageSubscription": "",
"Manage_Books": "Gestisci Libri",
"Manage_Emails": "Gestisci email",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Piano alimentare",
"Meal_Plan_Days": "Piani alimentari futuri",
"Meal_Type": "Tipo di pasto",

View File

@@ -199,6 +199,7 @@
"ManageSubscription": "",
"Manage_Books": "Tvarkyti knygas",
"Manage_Emails": "",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Maisto planas",
"Meal_Plan_Days": "",
"Meal_Type": "",

View File

@@ -194,6 +194,7 @@
"ManageSubscription": "",
"Manage_Books": "Administrer bøker",
"Manage_Emails": "Administrer e-poster",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Måltidsplan",
"Meal_Plan_Days": "Fremtidige måltidsplaner",
"Meal_Type": "Måltidstype",

View File

@@ -198,6 +198,7 @@
"ManageSubscription": "",
"Manage_Books": "Beheer boeken",
"Manage_Emails": "E-mail beheren",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Maaltijdplan",
"Meal_Plan_Days": "Toekomstige maaltijdplannen",
"Meal_Type": "Maaltype",

View File

@@ -217,6 +217,7 @@
"ManageSubscription": "",
"Manage_Books": "Zarządzaj książkami",
"Manage_Emails": "Zarządzaj e-mailami",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Plan posiłków",
"Meal_Plan_Days": "Przyszłe plany posiłków",
"Meal_Type": "Rodzaj posiłku",

View File

@@ -157,6 +157,7 @@
"Make_Ingredient": "Fazer ingrediente",
"ManageSubscription": "",
"Manage_Books": "Gerenciar Livros",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Plano de Refeição",
"Meal_Plan_Days": "Planos de alimentação futuros",
"Meal_Type": "Tipo de refeição",

View File

@@ -209,6 +209,7 @@
"ManageSubscription": "",
"Manage_Books": "Gerenciar Livros",
"Manage_Emails": "Gerenciar Emails",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Plano de Refeição",
"Meal_Plan_Days": "Planos de refeição futuros",
"Meal_Type": "Tipo de Comida",

View File

@@ -191,6 +191,7 @@
"ManageSubscription": "",
"Manage_Books": "Gestionarea cărților",
"Manage_Emails": "Gestionarea e-mailurilor",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Plan de alimentare",
"Meal_Plan_Days": "Planuri de alimentație pe viitor",
"Meal_Type": "Tipul mesei",

View File

@@ -146,6 +146,7 @@
"Make_Ingredient": "Создание инградиента",
"ManageSubscription": "",
"Manage_Books": "Управление книгами",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Планирование блюд",
"Meal_Plan_Days": "Планы питания на будущее",
"Meal_Type": "Тип питания",

View File

@@ -142,6 +142,7 @@
"Make_Ingredient": "Ustvari sestavino",
"ManageSubscription": "",
"Manage_Books": "Upravljaj knjige",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Načrt obroka",
"Meal_Plan_Days": "Načrt za prihodnje obroke",
"Meal_Type": "Tip obroka",

View File

@@ -217,6 +217,7 @@
"ManageSubscription": "",
"Manage_Books": "Hantera böcker",
"Manage_Emails": "Hantera mejladresser",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Måltidsplanering",
"Meal_Plan_Days": "Framtida måltidsplaner",
"Meal_Type": "Måltidstyp",

View File

@@ -216,6 +216,7 @@
"ManageSubscription": "",
"Manage_Books": "Kitapları Yönet",
"Manage_Emails": "E-postaları Yönet",
"MealPlanShoppingHelp": "",
"Meal_Plan": "Yemek Planı",
"Meal_Plan_Days": "Gelecek yemek planları",
"Meal_Type": "Yemek türü",

View File

@@ -171,6 +171,7 @@
"Make_Ingredient": "",
"ManageSubscription": "",
"Manage_Books": "Управління Книжкою",
"MealPlanShoppingHelp": "",
"Meal_Plan": "План Харчування",
"Meal_Plan_Days": "Майбутній план харчування",
"Meal_Type": "Тип страви",

View File

@@ -212,6 +212,7 @@
"ManageSubscription": "",
"Manage_Books": "管理书籍",
"Manage_Emails": "管理电子邮件",
"MealPlanShoppingHelp": "",
"Meal_Plan": "用餐计划",
"Meal_Plan_Days": "未来的用餐计划",
"Meal_Type": "用餐类型",

View File

@@ -69,6 +69,7 @@
"Logout": "",
"ManageSubscription": "",
"Manage_Books": "管理書籍",
"MealPlanShoppingHelp": "",
"Meal_Plan": "膳食計劃",
"Messages": "",
"Miscellaneous": "",

View File

@@ -1244,7 +1244,7 @@ export interface ApiShoppingListEntryBulkCreateRequest {
}
export interface ApiShoppingListEntryCreateRequest {
shoppingListEntry: Omit<ShoppingListEntry, 'recipe_mealplan'|'created_by'|'created_at'|'updated_at'>;
shoppingListEntry: Omit<ShoppingListEntry, 'list_recipe_data'|'created_by'|'created_at'|'updated_at'>;
}
export interface ApiShoppingListEntryDestroyRequest {
@@ -1260,7 +1260,7 @@ export interface ApiShoppingListEntryListRequest {
export interface ApiShoppingListEntryPartialUpdateRequest {
id: number;
patchedShoppingListEntry?: Omit<PatchedShoppingListEntry, 'recipe_mealplan'|'created_by'|'created_at'|'updated_at'>;
patchedShoppingListEntry?: Omit<PatchedShoppingListEntry, 'list_recipe_data'|'created_by'|'created_at'|'updated_at'>;
}
export interface ApiShoppingListEntryRetrieveRequest {
@@ -1269,7 +1269,7 @@ export interface ApiShoppingListEntryRetrieveRequest {
export interface ApiShoppingListEntryUpdateRequest {
id: number;
shoppingListEntry: Omit<ShoppingListEntry, 'recipe_mealplan'|'created_by'|'created_at'|'updated_at'>;
shoppingListEntry: Omit<ShoppingListEntry, 'list_recipe_data'|'created_by'|'created_at'|'updated_at'>;
}
export interface ApiShoppingListRecipeBulkCreateEntriesCreateRequest {
@@ -1278,7 +1278,7 @@ export interface ApiShoppingListRecipeBulkCreateEntriesCreateRequest {
}
export interface ApiShoppingListRecipeCreateRequest {
shoppingListRecipe: Omit<ShoppingListRecipe, 'recipe_name'|'mealplan_note'|'mealplan_from_date'|'mealplan_type'>;
shoppingListRecipe: Omit<ShoppingListRecipe, 'recipe_data'|'meal_plan_data'>;
}
export interface ApiShoppingListRecipeDestroyRequest {
@@ -1293,7 +1293,7 @@ export interface ApiShoppingListRecipeListRequest {
export interface ApiShoppingListRecipePartialUpdateRequest {
id: number;
patchedShoppingListRecipe?: Omit<PatchedShoppingListRecipe, 'recipe_name'|'mealplan_note'|'mealplan_from_date'|'mealplan_type'>;
patchedShoppingListRecipe?: Omit<PatchedShoppingListRecipe, 'recipe_data'|'meal_plan_data'>;
}
export interface ApiShoppingListRecipeRetrieveRequest {
@@ -1302,7 +1302,7 @@ export interface ApiShoppingListRecipeRetrieveRequest {
export interface ApiShoppingListRecipeUpdateRequest {
id: number;
shoppingListRecipe: Omit<ShoppingListRecipe, 'recipe_name'|'mealplan_note'|'mealplan_from_date'|'mealplan_type'>;
shoppingListRecipe: Omit<ShoppingListRecipe, 'recipe_data'|'meal_plan_data'>;
}
export interface ApiSpaceListRequest {

View File

@@ -95,7 +95,7 @@ export interface PatchedShoppingListEntry {
* @type {ShoppingListRecipe}
* @memberof PatchedShoppingListEntry
*/
readonly recipeMealplan?: ShoppingListRecipe;
readonly listRecipeData?: ShoppingListRecipe;
/**
*
* @type {User}
@@ -126,6 +126,12 @@ export interface PatchedShoppingListEntry {
* @memberof PatchedShoppingListEntry
*/
delayUntil?: Date | null;
/**
* If a mealplan id is given try to find existing or create new ShoppingListRecipe with that meal plan and link entry to it
* @type {number}
* @memberof PatchedShoppingListEntry
*/
mealplanId?: number;
}
/**
@@ -152,12 +158,13 @@ export function PatchedShoppingListEntryFromJSONTyped(json: any, ignoreDiscrimin
'amount': json['amount'] == null ? undefined : json['amount'],
'order': json['order'] == null ? undefined : json['order'],
'checked': json['checked'] == null ? undefined : json['checked'],
'recipeMealplan': json['recipe_mealplan'] == null ? undefined : ShoppingListRecipeFromJSON(json['recipe_mealplan']),
'listRecipeData': json['list_recipe_data'] == null ? undefined : ShoppingListRecipeFromJSON(json['list_recipe_data']),
'createdBy': json['created_by'] == null ? undefined : UserFromJSON(json['created_by']),
'createdAt': json['created_at'] == null ? undefined : (new Date(json['created_at'])),
'updatedAt': json['updated_at'] == null ? undefined : (new Date(json['updated_at'])),
'completedAt': json['completed_at'] == null ? undefined : (new Date(json['completed_at'])),
'delayUntil': json['delay_until'] == null ? undefined : (new Date(json['delay_until'])),
'mealplanId': json['mealplan_id'] == null ? undefined : json['mealplan_id'],
};
}
@@ -165,7 +172,7 @@ export function PatchedShoppingListEntryToJSON(json: any): PatchedShoppingListEn
return PatchedShoppingListEntryToJSONTyped(json, false);
}
export function PatchedShoppingListEntryToJSONTyped(value?: Omit<PatchedShoppingListEntry, 'recipe_mealplan'|'created_by'|'created_at'|'updated_at'> | null, ignoreDiscriminator: boolean = false): any {
export function PatchedShoppingListEntryToJSONTyped(value?: Omit<PatchedShoppingListEntry, 'list_recipe_data'|'created_by'|'created_at'|'updated_at'> | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
@@ -181,6 +188,7 @@ export function PatchedShoppingListEntryToJSONTyped(value?: Omit<PatchedShopping
'checked': value['checked'],
'completed_at': value['completedAt'] == null ? undefined : ((value['completedAt'] as any).toISOString()),
'delay_until': value['delayUntil'] == null ? undefined : ((value['delayUntil'] as any).toISOString()),
'mealplan_id': value['mealplanId'],
};
}

View File

@@ -13,6 +13,21 @@
*/
import { mapValues } from '../runtime';
import type { MealPlan } from './MealPlan';
import {
MealPlanFromJSON,
MealPlanFromJSONTyped,
MealPlanToJSON,
MealPlanToJSONTyped,
} from './MealPlan';
import type { RecipeOverview } from './RecipeOverview';
import {
RecipeOverviewFromJSON,
RecipeOverviewFromJSONTyped,
RecipeOverviewToJSON,
RecipeOverviewToJSONTyped,
} from './RecipeOverview';
/**
*
* @export
@@ -25,12 +40,6 @@ export interface PatchedShoppingListRecipe {
* @memberof PatchedShoppingListRecipe
*/
id?: number;
/**
*
* @type {string}
* @memberof PatchedShoppingListRecipe
*/
readonly recipeName?: string;
/**
*
* @type {string}
@@ -43,36 +52,30 @@ export interface PatchedShoppingListRecipe {
* @memberof PatchedShoppingListRecipe
*/
recipe?: number | null;
/**
*
* @type {RecipeOverview}
* @memberof PatchedShoppingListRecipe
*/
readonly recipeData?: RecipeOverview;
/**
*
* @type {number}
* @memberof PatchedShoppingListRecipe
*/
mealplan?: number | null;
/**
*
* @type {MealPlan}
* @memberof PatchedShoppingListRecipe
*/
readonly mealPlanData?: MealPlan;
/**
*
* @type {number}
* @memberof PatchedShoppingListRecipe
*/
servings?: number;
/**
*
* @type {string}
* @memberof PatchedShoppingListRecipe
*/
readonly mealplanNote?: string;
/**
*
* @type {Date}
* @memberof PatchedShoppingListRecipe
*/
readonly mealplanFromDate?: Date;
/**
*
* @type {string}
* @memberof PatchedShoppingListRecipe
*/
readonly mealplanType?: string;
}
/**
@@ -93,14 +96,12 @@ export function PatchedShoppingListRecipeFromJSONTyped(json: any, ignoreDiscrimi
return {
'id': json['id'] == null ? undefined : json['id'],
'recipeName': json['recipe_name'] == null ? undefined : json['recipe_name'],
'name': json['name'] == null ? undefined : json['name'],
'recipe': json['recipe'] == null ? undefined : json['recipe'],
'recipeData': json['recipe_data'] == null ? undefined : RecipeOverviewFromJSON(json['recipe_data']),
'mealplan': json['mealplan'] == null ? undefined : json['mealplan'],
'mealPlanData': json['meal_plan_data'] == null ? undefined : MealPlanFromJSON(json['meal_plan_data']),
'servings': json['servings'] == null ? undefined : json['servings'],
'mealplanNote': json['mealplan_note'] == null ? undefined : json['mealplan_note'],
'mealplanFromDate': json['mealplan_from_date'] == null ? undefined : (new Date(json['mealplan_from_date'])),
'mealplanType': json['mealplan_type'] == null ? undefined : json['mealplan_type'],
};
}
@@ -108,7 +109,7 @@ export function PatchedShoppingListRecipeToJSON(json: any): PatchedShoppingListR
return PatchedShoppingListRecipeToJSONTyped(json, false);
}
export function PatchedShoppingListRecipeToJSONTyped(value?: Omit<PatchedShoppingListRecipe, 'recipe_name'|'mealplan_note'|'mealplan_from_date'|'mealplan_type'> | null, ignoreDiscriminator: boolean = false): any {
export function PatchedShoppingListRecipeToJSONTyped(value?: Omit<PatchedShoppingListRecipe, 'recipe_data'|'meal_plan_data'> | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}

View File

@@ -95,7 +95,7 @@ export interface ShoppingListEntry {
* @type {ShoppingListRecipe}
* @memberof ShoppingListEntry
*/
readonly recipeMealplan: ShoppingListRecipe;
readonly listRecipeData: ShoppingListRecipe;
/**
*
* @type {User}
@@ -126,6 +126,12 @@ export interface ShoppingListEntry {
* @memberof ShoppingListEntry
*/
delayUntil?: Date | null;
/**
* If a mealplan id is given try to find existing or create new ShoppingListRecipe with that meal plan and link entry to it
* @type {number}
* @memberof ShoppingListEntry
*/
mealplanId?: number;
}
/**
@@ -134,7 +140,7 @@ export interface ShoppingListEntry {
export function instanceOfShoppingListEntry(value: object): value is ShoppingListEntry {
if (!('food' in value) || value['food'] === undefined) return false;
if (!('amount' in value) || value['amount'] === undefined) return false;
if (!('recipeMealplan' in value) || value['recipeMealplan'] === undefined) return false;
if (!('listRecipeData' in value) || value['listRecipeData'] === undefined) return false;
if (!('createdBy' in value) || value['createdBy'] === undefined) return false;
if (!('createdAt' in value) || value['createdAt'] === undefined) return false;
if (!('updatedAt' in value) || value['updatedAt'] === undefined) return false;
@@ -158,12 +164,13 @@ export function ShoppingListEntryFromJSONTyped(json: any, ignoreDiscriminator: b
'amount': json['amount'],
'order': json['order'] == null ? undefined : json['order'],
'checked': json['checked'] == null ? undefined : json['checked'],
'recipeMealplan': ShoppingListRecipeFromJSON(json['recipe_mealplan']),
'listRecipeData': ShoppingListRecipeFromJSON(json['list_recipe_data']),
'createdBy': UserFromJSON(json['created_by']),
'createdAt': (new Date(json['created_at'])),
'updatedAt': (new Date(json['updated_at'])),
'completedAt': json['completed_at'] == null ? undefined : (new Date(json['completed_at'])),
'delayUntil': json['delay_until'] == null ? undefined : (new Date(json['delay_until'])),
'mealplanId': json['mealplan_id'] == null ? undefined : json['mealplan_id'],
};
}
@@ -171,7 +178,7 @@ export function ShoppingListEntryToJSON(json: any): ShoppingListEntry {
return ShoppingListEntryToJSONTyped(json, false);
}
export function ShoppingListEntryToJSONTyped(value?: Omit<ShoppingListEntry, 'recipe_mealplan'|'created_by'|'created_at'|'updated_at'> | null, ignoreDiscriminator: boolean = false): any {
export function ShoppingListEntryToJSONTyped(value?: Omit<ShoppingListEntry, 'list_recipe_data'|'created_by'|'created_at'|'updated_at'> | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
@@ -187,6 +194,7 @@ export function ShoppingListEntryToJSONTyped(value?: Omit<ShoppingListEntry, 're
'checked': value['checked'],
'completed_at': value['completedAt'] == null ? undefined : ((value['completedAt'] as any).toISOString()),
'delay_until': value['delayUntil'] == null ? undefined : ((value['delayUntil'] as any).toISOString()),
'mealplan_id': value['mealplanId'],
};
}

View File

@@ -13,6 +13,21 @@
*/
import { mapValues } from '../runtime';
import type { MealPlan } from './MealPlan';
import {
MealPlanFromJSON,
MealPlanFromJSONTyped,
MealPlanToJSON,
MealPlanToJSONTyped,
} from './MealPlan';
import type { RecipeOverview } from './RecipeOverview';
import {
RecipeOverviewFromJSON,
RecipeOverviewFromJSONTyped,
RecipeOverviewToJSON,
RecipeOverviewToJSONTyped,
} from './RecipeOverview';
/**
*
* @export
@@ -25,12 +40,6 @@ export interface ShoppingListRecipe {
* @memberof ShoppingListRecipe
*/
id?: number;
/**
*
* @type {string}
* @memberof ShoppingListRecipe
*/
readonly recipeName: string;
/**
*
* @type {string}
@@ -43,47 +52,39 @@ export interface ShoppingListRecipe {
* @memberof ShoppingListRecipe
*/
recipe?: number | null;
/**
*
* @type {RecipeOverview}
* @memberof ShoppingListRecipe
*/
readonly recipeData: RecipeOverview;
/**
*
* @type {number}
* @memberof ShoppingListRecipe
*/
mealplan?: number | null;
/**
*
* @type {MealPlan}
* @memberof ShoppingListRecipe
*/
readonly mealPlanData: MealPlan;
/**
*
* @type {number}
* @memberof ShoppingListRecipe
*/
servings: number;
/**
*
* @type {string}
* @memberof ShoppingListRecipe
*/
readonly mealplanNote: string;
/**
*
* @type {Date}
* @memberof ShoppingListRecipe
*/
readonly mealplanFromDate: Date;
/**
*
* @type {string}
* @memberof ShoppingListRecipe
*/
readonly mealplanType: string;
}
/**
* Check if a given object implements the ShoppingListRecipe interface.
*/
export function instanceOfShoppingListRecipe(value: object): value is ShoppingListRecipe {
if (!('recipeName' in value) || value['recipeName'] === undefined) return false;
if (!('recipeData' in value) || value['recipeData'] === undefined) return false;
if (!('mealPlanData' in value) || value['mealPlanData'] === undefined) return false;
if (!('servings' in value) || value['servings'] === undefined) return false;
if (!('mealplanNote' in value) || value['mealplanNote'] === undefined) return false;
if (!('mealplanFromDate' in value) || value['mealplanFromDate'] === undefined) return false;
if (!('mealplanType' in value) || value['mealplanType'] === undefined) return false;
return true;
}
@@ -98,14 +99,12 @@ export function ShoppingListRecipeFromJSONTyped(json: any, ignoreDiscriminator:
return {
'id': json['id'] == null ? undefined : json['id'],
'recipeName': json['recipe_name'],
'name': json['name'] == null ? undefined : json['name'],
'recipe': json['recipe'] == null ? undefined : json['recipe'],
'recipeData': RecipeOverviewFromJSON(json['recipe_data']),
'mealplan': json['mealplan'] == null ? undefined : json['mealplan'],
'mealPlanData': MealPlanFromJSON(json['meal_plan_data']),
'servings': json['servings'],
'mealplanNote': json['mealplan_note'],
'mealplanFromDate': (new Date(json['mealplan_from_date'])),
'mealplanType': json['mealplan_type'],
};
}
@@ -113,7 +112,7 @@ export function ShoppingListRecipeToJSON(json: any): ShoppingListRecipe {
return ShoppingListRecipeToJSONTyped(json, false);
}
export function ShoppingListRecipeToJSONTyped(value?: Omit<ShoppingListRecipe, 'recipe_name'|'mealplan_note'|'mealplan_from_date'|'mealplan_type'> | null, ignoreDiscriminator: boolean = false): any {
export function ShoppingListRecipeToJSONTyped(value?: Omit<ShoppingListRecipe, 'recipe_data'|'meal_plan_data'> | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}

View File

@@ -15,6 +15,7 @@ import {
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import {isDelayed} from "@/utils/logic_utils";
import {DateTime} from "luxon";
const _STORE_ID = "shopping_store"
const UNDEFINED_CATEGORY = 'shopping_undefined_category'
@@ -157,7 +158,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let items: IShoppingListFood[] = []
entries.value.forEach(shoppingListEntry => {
if (shoppingListEntry.recipeMealplan && shoppingListEntry.recipeMealplan.mealplan == mealPlanId) {
if (shoppingListEntry.listRecipe && shoppingListEntry.listRecipeData.mealplan == mealPlanId) {
items.push({
food: shoppingListEntry.food,
entries: new Map<number, ShoppingListEntry>().set(shoppingListEntry.id!, shoppingListEntry)
@@ -307,8 +308,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let recipes = [] as ShoppingListRecipe[]
entries.value.forEach(e => {
if (e.recipeMealplan != null && recipes.findIndex(x => x.id == e.recipeMealplan.id) == -1) {
recipes.push(e.recipeMealplan)
if (e.listRecipe != null && recipes.findIndex(x => x.id == e.listRecipe) == -1) {
recipes.push(e.listRecipeData)
}
})
@@ -331,8 +332,14 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
groupingKey = entry.food?.supermarketCategory?.name
} else if (group == ShoppingGroupingOptions.CREATED_BY) {
groupingKey = entry.createdBy.displayName
} else if (group == ShoppingGroupingOptions.RECIPE && entry.recipeMealplan != null) {
groupingKey = entry.recipeMealplan.recipeName
} else if (group == ShoppingGroupingOptions.RECIPE && entry.listRecipeData != null) {
if (entry.listRecipeData.recipeData != null) {
groupingKey = entry.listRecipeData.recipeData.name
if (entry.listRecipeData.mealPlanData != null) {
groupingKey += ' - ' + entry.listRecipeData.mealPlanData.mealType.name + ' - ' + DateTime.fromJSDate(entry.listRecipeData.mealPlanData.fromDate).toLocaleString(DateTime.DATE_SHORT)
}
}
}
if (!structure.categories.has(groupingKey) && !(group == ShoppingGroupingOptions.CATEGORY && useUserPreferenceStore().deviceSettings.shopping_show_selected_supermarket_only)) {