mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-02 12:49:02 -05:00
shopping from meal plan edit
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
73
vue3/src/components/inputs/ShoppingListEntryInput.vue
Normal file
73
vue3/src/components/inputs/ShoppingListEntryInput.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user