various meal plan fixes

This commit is contained in:
vabene1111
2025-01-02 09:21:52 +01:00
parent f97d8ffdfd
commit 00ae511076
5 changed files with 89 additions and 55 deletions

View File

@@ -21,7 +21,7 @@
<div class="align-self-center"> <div class="align-self-center">
<v-btn variant="flat" icon=""> <v-btn variant="flat" icon="">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
<model-edit-dialog model="MealPlan"></model-edit-dialog> <model-edit-dialog model="MealPlan" :item-defaults="{fromDate: mealPlanGridItem.date.toJSDate()}" :close-after-create="false" :close-after-save="false"></model-edit-dialog>
</v-btn> </v-btn>
</div> </div>
</div> </div>

View File

@@ -97,6 +97,18 @@ const calendarItemHeight = computed(() => {
* watch calendar date and load entries accordingly * watch calendar date and load entries accordingly
*/ */
watch(calendarDate, () => { watch(calendarDate, () => {
refreshVisiblePeriod(false)
})
onMounted(() => {
refreshVisiblePeriod(true)
})
/**
* refresh data for the currently visible period
* @param startDateUnknown when the calendar initially loads the date is set to today but the visible period might be larger. If set loads the period day count for the past as well
*/
function refreshVisiblePeriod(startDateUnknown: boolean) {
let daysInPeriod = 7 let daysInPeriod = 7
if (useUserPreferenceStore().deviceSettings.mealplan_displayPeriod == 'month') { if (useUserPreferenceStore().deviceSettings.mealplan_displayPeriod == 'month') {
daysInPeriod = 31 daysInPeriod = 31
@@ -105,13 +117,14 @@ watch(calendarDate, () => {
} }
let days = useUserPreferenceStore().deviceSettings.mealplan_displayPeriodCount * daysInPeriod let days = useUserPreferenceStore().deviceSettings.mealplan_displayPeriodCount * daysInPeriod
useMealPlanStore().refreshFromAPI(calendarDate.value, DateTime.now().plus({days: days}).toJSDate())
})
onMounted(() => { // load backwards to as on initial
// initial load for next 30 days if (startDateUnknown) {
useMealPlanStore().refreshFromAPI(calendarDate.value, DateTime.now().plus({days: 30}).toJSDate()) useMealPlanStore().refreshFromAPI(DateTime.fromJSDate(calendarDate.value).minus({days: days}).toJSDate(), DateTime.now().plus({days: days}).toJSDate())
}) } else {
useMealPlanStore().refreshFromAPI(calendarDate.value, DateTime.now().plus({days: days}).toJSDate())
}
}
/** /**
* handle drop event for calendar items on fields * handle drop event for calendar items on fields
@@ -124,8 +137,8 @@ function dropCalendarItemOnDate(undefinedItem: IMealPlanNormalizedCalendarItem,
if (currentlyDraggedMealplan.value.originalItem.mealPlan.id != undefined) { if (currentlyDraggedMealplan.value.originalItem.mealPlan.id != undefined) {
let mealPlan = useMealPlanStore().plans.get(currentlyDraggedMealplan.value.originalItem.mealPlan.id) let mealPlan = useMealPlanStore().plans.get(currentlyDraggedMealplan.value.originalItem.mealPlan.id)
if (mealPlan != undefined) { if (mealPlan != undefined) {
let fromToDiff = {days: 1} let fromToDiff = {days: 0}
if (mealPlan.toDate) { if (mealPlan.toDate && mealPlan.toDate > mealPlan.fromDate) {
fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days') fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days')
} }
// create copy of item if control is pressed // create copy of item if control is pressed

View File

@@ -2,8 +2,8 @@
<model-editor-base <model-editor-base
:loading="loading" :loading="loading"
:dialog="dialog" :dialog="dialog"
@save="saveObject" @save="saveObject().then((obj:MealPlan) => { useMealPlanStore().plans.set(obj.id, obj); loadShoppingListEntries()})"
@delete="deleteObject" @delete="useMealPlanStore().plans.delete(editingObj.id); deleteObject()"
@close="emit('close')" @close="emit('close')"
:is-update="isUpdate()" :is-update="isUpdate()"
:is-changed="editingObjChanged" :is-changed="editingObjChanged"
@@ -53,16 +53,15 @@
<!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>--> <!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>-->
<!--TODO create days input with +/- synced to date --> <!--TODO create days input with +/- synced to date -->
<recipe-card :recipe="editingObj.recipe" v-if="editingObj && editingObj.recipe"></recipe-card> <recipe-card :recipe="editingObj.recipe" v-if="editingObj && editingObj.recipe"></recipe-card>
<v-checkbox :label="$t('AddToShopping')" v-model="editingObj.addshopping" hide-details v-if="editingObj.recipe && !isUpdate()"></v-checkbox>
<!-- TODO review shopping before add -->
</v-col> </v-col>
</v-row> </v-row>
<v-row dense> <v-row dense>
<v-col cols="12" md="6"> <v-col cols="12">
<v-textarea :label="$t('Note')" v-model="editingObj.note" rows="3"></v-textarea> <v-textarea :label="$t('Note')" v-model="editingObj.note" rows="3"></v-textarea>
</v-col> </v-col>
<v-col cols="12" md="6" v-if="!isUpdate()">
<v-checkbox :label="$t('AddToShopping')" v-model="editingObj.addshopping" hide-details></v-checkbox>
<!-- <v-checkbox :label="$t('review_shopping')" v-model="addToShopping" hide-details></v-checkbox>-->
</v-col>
</v-row> </v-row>
</v-form> </v-form>
</v-tabs-window-item> </v-tabs-window-item>
@@ -70,25 +69,22 @@
<v-tabs-window-item value="shopping"> <v-tabs-window-item value="shopping">
<closable-help-alert class="mb-2" :text="$t('MealPlanShoppingHelp')"></closable-help-alert> <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-row v-if="isUpdate()" dense style="max-height: 75vh" class="overflow-y-scroll">
<v-col> <v-col>
<shopping-list-entry-input :loading="useShoppingStore().currentlyUpdating" :meal-plan="editingObj"></shopping-list-entry-input> <shopping-list-entry-input :loading="useShoppingStore().currentlyUpdating" :meal-plan="editingObj"></shopping-list-entry-input>
<v-list v-if="editingObj.id"> <v-list v-if="editingObj.id">
<shopping-line-item <shopping-line-item
v-for="slf in useShoppingStore().getMealPlanEntries(editingObj.id)" v-for="slf in useShoppingStore().getMealPlanEntries(editingObj.id)"
:shopping-list-food="slf" :shopping-list-food="slf"
hide-info-row hide-info-row
:key="slf.food.id"
></shopping-line-item> ></shopping-line-item>
</v-list> </v-list>
</v-col> </v-col>
</v-row> </v-row>
</v-tabs-window-item> </v-tabs-window-item>
</v-tabs-window> </v-tabs-window>
</v-card-text> </v-card-text>
</model-editor-base> </model-editor-base>
@@ -96,7 +92,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, PropType, ref} from "vue"; import {nextTick, onMounted, PropType, ref} from "vue";
import {ApiApi, MealPlan, MealType, ShoppingListRecipe} from "@/openapi"; import {ApiApi, MealPlan, MealType, ShoppingListRecipe} from "@/openapi";
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue"; import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions"; import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
@@ -112,6 +108,7 @@ import ShoppingLineItem from "@/components/display/ShoppingLineItem.vue";
import {useShoppingStore} from "@/stores/ShoppingStore"; import {useShoppingStore} from "@/stores/ShoppingStore";
import ShoppingListEntryInput from "@/components/inputs/ShoppingListEntryInput.vue"; import ShoppingListEntryInput from "@/components/inputs/ShoppingListEntryInput.vue";
import ClosableHelpAlert from "@/components/display/ClosableHelpAlert.vue"; import ClosableHelpAlert from "@/components/display/ClosableHelpAlert.vue";
import {useMealPlanStore} from "@/stores/MealPlanStore";
const props = defineProps({ const props = defineProps({
item: {type: {} as PropType<MealPlan>, required: false, default: null}, item: {type: {} as PropType<MealPlan>, required: false, default: null},
@@ -121,13 +118,23 @@ const props = defineProps({
}) })
const emit = defineEmits(['create', 'save', 'delete', 'close']) const emit = defineEmits(['create', 'save', 'delete', 'close'])
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, applyItemDefaults, loading, editingObj, editingObjChanged, modelClass} = useModelEditorFunctions<MealPlan>('MealPlan', emit) const {
setupState,
deleteObject,
saveObject,
isUpdate,
editingObjName,
applyItemDefaults,
loading,
editingObj,
editingObjChanged,
modelClass
} = useModelEditorFunctions<MealPlan>('MealPlan', emit)
// object specific data (for selects/display) // object specific data (for selects/display)
const tab = ref('plan') const tab = ref('plan')
const dateRangeValue = ref([] as Date[]) const dateRangeValue = ref([] as Date[])
const shoppingListRecipe = ref<ShoppingListRecipe | undefined>(undefined)
onMounted(() => { onMounted(() => {
const api = new ApiApi() const api = new ApiApi()
@@ -155,20 +162,22 @@ onMounted(() => {
editingObj.value.servings = 1 editingObj.value.servings = 1
editingObj.value.mealType = defaultMealType editingObj.value.mealType = defaultMealType
editingObj.value.addshopping = !!useUserPreferenceStore().userSettings.mealplanAutoaddShopping editingObj.value.addshopping = useUserPreferenceStore().userSettings.mealplanAutoaddShopping
applyItemDefaults(props.itemDefaults) applyItemDefaults(props.itemDefaults)
if (editingObj.value.toDate < editingObj.value.fromDate) {
editingObj.value.toDate = editingObj.value.fromDate
}
initializeDateRange() initializeDateRange()
nextTick(() => {
editingObjChanged.value = false
})
}, existingItemFunction: () => { }, existingItemFunction: () => {
initializeDateRange() initializeDateRange()
useShoppingStore().refreshFromAPI(editingObj.value.id!) loadShoppingListEntries()
api.apiShoppingListRecipeList({mealplan: editingObj.value.id!}).then(r => {
if (r.results.length > 0) {
shoppingListRecipe.value = r.results[0]
}
})
} }
},) },)
}) })
@@ -181,13 +190,24 @@ onMounted(() => {
function updateDate() { function updateDate() {
if (dateRangeValue.value != null) { if (dateRangeValue.value != null) {
editingObj.value.fromDate = dateRangeValue.value[0] editingObj.value.fromDate = dateRangeValue.value[0]
editingObj.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1] if (dateRangeValue.value[dateRangeValue.value.length - 1] > editingObj.value.fromDate) {
editingObj.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
} else {
editingObj.value.toDate = editingObj.value.fromDate
}
} else { } else {
useMessageStore().addMessage(MessageType.WARNING, 'Missing Date', 7000) useMessageStore().addMessage(MessageType.WARNING, 'Missing Date', 7000)
return return
} }
} }
/**
* load all shopping list entries associated with meal plan
*/
function loadShoppingListEntries() {
useShoppingStore().refreshFromAPI(editingObj.value.id!)
}
/** /**
* initialize the dateRange selector when the editingObject is initialized * initialize the dateRange selector when the editingObject is initialized
*/ */
@@ -204,24 +224,6 @@ 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> </script>
<style scoped> <style scoped>

View File

@@ -19,6 +19,10 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
const {t} = useI18n() const {t} = useI18n()
/**
* watch editing object to detect changes
* set editingObjChanged to true when a change is detected
*/
watch(() => editingObj.value, (newValue, oldValue) => { watch(() => editingObj.value, (newValue, oldValue) => {
if (Object.keys(oldValue).length > 0) { if (Object.keys(oldValue).length > 0) {
editingObjChanged.value = true editingObjChanged.value = true
@@ -55,7 +59,7 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
function applyItemDefaults(itemDefaults: T) { function applyItemDefaults(itemDefaults: T) {
if (Object.keys(itemDefaults).length > 0) { if (Object.keys(itemDefaults).length > 0) {
Object.keys(itemDefaults).forEach(k => { Object.keys(itemDefaults).forEach(k => {
console.log('applying default ', k) console.log('applying default ', k, itemDefaults[k])
editingObj.value[k] = itemDefaults[k] editingObj.value[k] = itemDefaults[k]
}) })
} }
@@ -209,8 +213,9 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
function deleteObject() { function deleteObject() {
loading.value = true loading.value = true
modelClass.value.destroy(editingObj.value.id).then((r: any) => { return modelClass.value.destroy(editingObj.value.id).then((r: any) => {
emit('delete', editingObj.value) emit('delete', editingObj.value)
console.log('deleted')
editingObj.value = {} as T editingObj.value = {} as T
}).catch((err: any) => { }).catch((err: any) => {
useMessageStore().addError(ErrorMessageType.DELETE_ERROR, err) useMessageStore().addError(ErrorMessageType.DELETE_ERROR, err)

View File

@@ -55,10 +55,24 @@ export const useMealPlanStore = defineStore(_STORE_ID, () => {
currently_updating.value = [from_date, to_date] // certainly no perfect check but better than nothing currently_updating.value = [from_date, to_date] // certainly no perfect check but better than nothing
loading.value = true loading.value = true
const api = new ApiApi() const api = new ApiApi()
return api.apiMealPlanList({fromDate: DateTime.fromJSDate(from_date).toISODate() as string, toDate: DateTime.fromJSDate(to_date).toISODate() as string, pageSize: 100}).then(r => { return api.apiMealPlanList({
fromDate: DateTime.fromJSDate(from_date).toISODate() as string,
toDate: DateTime.fromJSDate(to_date).toISODate() as string,
pageSize: 100
}).then(r => {
let foundIds: number[] = []
r.results.forEach((p) => { r.results.forEach((p) => {
plans.value.set(p.id, p) plans.value.set(p.id!, p)
foundIds.push(p.id!)
}) })
// delete entries that no longer exist
plans.value.forEach(p => {
if (!foundIds.includes(p.id!)) {
plans.value.delete(p.id!)
}
})
currently_updating.value = [new Date(0), new Date(0)] currently_updating.value = [new Date(0), new Date(0)]
}).catch((err) => { }).catch((err) => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err) useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)