mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-10 00:28:22 -05:00
various meal plan fixes
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user