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>