meal plan editor tweaks

This commit is contained in:
vabene1111
2024-10-10 21:13:58 +02:00
parent 4f425fb99a
commit eee8ed70e7
8 changed files with 40 additions and 183 deletions

View File

@@ -1,153 +0,0 @@
<template>
<v-dialog activator="parent" v-model="dialog" max-width="1200">
<template v-slot:default="{ isActive }">
<v-card style="overflow: auto">
<v-card-title>Meal Plan Edit
<v-btn icon="fas fa-times" variant="flat" size="x-small" class="mt-2 float-right " @click="isActive.value = false"></v-btn>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-form>
<v-row>
<v-col cols="12" md="6">
<v-text-field label="Title" v-model="mutableMealPlan.title"></v-text-field>
<v-date-input
v-model="dateRangeValue"
label="Plan Date"
multiple="range"
prepend-icon=""
prepend-inner-icon="$calendar"
></v-date-input>
<v-input>
<v-btn-group elevation="1" class="w-100" divided border>
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,-1)"><i class="fa-solid fa-minus"></i></v-btn>
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, -1)"><i class="fa-solid fa-angles-left"></i></v-btn>
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, +1)"><i class="fa-solid fa-angles-right"></i></v-btn>
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,+1)"><i class="fa-solid fa-plus"></i></v-btn>
</v-btn-group>
</v-input>
<ModelSelect model="MealType" :allow-create="true" v-model="mutableMealPlan.mealType"></ModelSelect>
<v-number-input control-variant="split" :min="0" v-model="mutableMealPlan.servings" label="Servings"></v-number-input>
<ModelSelect model="User" :allow-create="false" v-model="mutableMealPlan.shared" item-label="displayName" mode="tags"></ModelSelect>
</v-col>
<v-col cols="12" md="6">
<ModelSelect model="Recipe" v-model="mutableMealPlan.recipe" @update:modelValue="mutableMealPlan.servings = mutableMealPlan.recipe?.servings ? mutableMealPlan.recipe?.servings : 1"></ModelSelect>
<!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>--> <!--TODO create days input with +/- synced to date -->
<recipe-card :recipe="mutableMealPlan.recipe" v-if="mutableMealPlan && mutableMealPlan.recipe"></recipe-card>
</v-col>
</v-row>
<v-row>
<v-col>
<v-textarea label="Note" v-model="mutableMealPlan.note"></v-textarea>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn color="error" @click="useMealPlanStore().deleteObject(mutableMealPlan); dialog = false">
Delete
</v-btn>
<v-btn color="success" class="ml-auto" @click="saveMealPlan">
Save
</v-btn>
</v-card-actions>
</v-card>
</template>
</v-dialog>
</template>
<script setup lang="ts">
import {onMounted, PropType, ref, watch, watchEffect} from "vue";
import {ApiApi, MealPlan, RecipeOverview} from "@/openapi";
import {DateTime} from "luxon";
import RecipeCard from "@/components/display/RecipeCard.vue";
import {useMealPlanStore} from "@/stores/MealPlanStore";
import {VNumberInput} from 'vuetify/labs/VNumberInput' //TODO remove once component is out of labs
import {VDateInput} from 'vuetify/labs/VDateInput' //TODO remove once component is out of labs
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {useMessageStore} from "@/stores/MessageStore";
import {adjustDateRangeLength, shiftDateRange} from "@/utils/date_utils";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
const props = defineProps(
{
mealPlan: {type: Object as PropType<MealPlan>, required: false},
}
)
const dialog = ref(false)
let mutableMealPlan = ref(newMealPlan())
const dateRangeValue = ref([] as Date[])
if (props.mealPlan != undefined) {
mutableMealPlan.value = props.mealPlan
}
/**
* once dialog is opened check if a meal plan prop is given, if so load it as the default values
*/
watch(dialog, () => {
if (dialog.value && props.mealPlan != undefined) {
mutableMealPlan.value = props.mealPlan
dateRangeValue.value = []
if (!dateRangeValue.value.includes(mutableMealPlan.value.fromDate)) {
dateRangeValue.value.push(mutableMealPlan.value.fromDate)
}
if (mutableMealPlan.value.toDate && !dateRangeValue.value.includes(mutableMealPlan.value.toDate)) {
dateRangeValue.value.push(mutableMealPlan.value.toDate)
}
} else {
mutableMealPlan.value = newMealPlan()
}
});
/**
* save meal plan into DB, parsing values from dateRange into meal plan object
*/
function saveMealPlan() {
if (mutableMealPlan.value != undefined) {
mutableMealPlan.value.recipe = mutableMealPlan.value.recipe as RecipeOverview
if (dateRangeValue.value != null) {
mutableMealPlan.value.fromDate = dateRangeValue.value[0]
mutableMealPlan.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
} else {
useMessageStore().addError('Missing Dates')
return
}
console.log('calling save method')
useMealPlanStore().createOrUpdate(mutableMealPlan.value).catch(err => {
// TODO handle error
}).finally(() => {
dialog.value = false
})
}
}
/**
* create new meal plan on current date
*/
function newMealPlan() {
// TODO load default meal type
return {
fromDate: DateTime.now().toJSDate(),
toDate: DateTime.now().toJSDate(),
servings: 1,
shared: useUserPreferenceStore().userSettings.planShare
} as MealPlan
}
</script>
<style src="@vueform/multiselect/themes/default.css"></style>
<style scoped>
</style>

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>
<meal-plan-dialog></meal-plan-dialog> <model-edit-dialog model="MealPlan"></model-edit-dialog>
</v-btn> </v-btn>
</div> </div>
</div> </div>
@@ -40,7 +40,7 @@
<v-list-item-subtitle> <v-list-item-subtitle>
{{ p.mealType.name }} {{ p.mealType.name }}
</v-list-item-subtitle> </v-list-item-subtitle>
<meal-plan-dialog :meal-plan="p"></meal-plan-dialog> <model-edit-dialog model="MealPlan" :item="p"></model-edit-dialog>
</v-list-item> </v-list-item>
</v-list> </v-list>
@@ -55,14 +55,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import {computed, onMounted, PropType, ref, toRefs} from 'vue' import {computed, onMounted, ref} from 'vue'
import RecipeCard from "@/components/display/RecipeCard.vue";
import {useDisplay} from "vuetify"; import {useDisplay} from "vuetify";
import {MealPlan, Recipe, RecipeOverview} from "@/openapi"; import {MealPlan} from "@/openapi";
import {useMealPlanStore} from "@/stores/MealPlanStore"; import {useMealPlanStore} from "@/stores/MealPlanStore";
import {DateTime} from "luxon"; import {DateTime} from "luxon";
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue";
import {homePageCols} from "@/utils/breakpoint_utils"; import {homePageCols} from "@/utils/breakpoint_utils";
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
const loading = ref(false) const loading = ref(false)

View File

@@ -17,9 +17,8 @@
</span> </span>
</div> </div>
</div> </div>
<meal-plan-dialog :meal-plan="mealPlan"></meal-plan-dialog> <model-edit-dialog model="MealPlan" :item="mealPlan" @delete="(args: MealPlan) => emit('delete', args)"></model-edit-dialog>
</v-card-text> </v-card-text>
</v-card> </v-card>
</template> </template>
@@ -28,12 +27,16 @@
import {computed, PropType} from "vue"; import {computed, PropType} from "vue";
import {IMealPlanNormalizedCalendarItem} from "@/types/MealPlan"; import {IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
import RecipeImage from "@/components/display/RecipeImage.vue"; import RecipeImage from "@/components/display/RecipeImage.vue";
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue"; import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
import {MealPlan} from "@/openapi";
const emit = defineEmits({ const emit = defineEmits({
onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => { onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => {
return true return true
}, },
delete: (value: MealPlan) => {
return true
}
}) })
let props = defineProps({ let props = defineProps({

View File

@@ -2,22 +2,25 @@
<v-row class="h-100"> <v-row class="h-100">
<v-col> <v-col>
<!-- TODO add hint about CTRL key while drag/drop --> <!-- TODO add hint about CTRL key while drag/drop -->
<v-btn>
<model-edit-dialog model="MealPlan"></model-edit-dialog>
</v-btn>
<CalendarView <CalendarView
:items="planItems" :items="planItems"
class="theme-default" class="theme-default"
:item-content-height="calendarItemHeight" :item-content-height="calendarItemHeight"
:enable-drag-drop="true" :enable-drag-drop="true"
@dropOnDate="dropCalendarItemOnDate"> @dropOnDate="dropCalendarItemOnDate"
@click-date="">
<template #header="{ headerProps }"> <template #header="{ headerProps }">
<CalendarViewHeader :header-props="headerProps"/> <CalendarViewHeader :header-props="headerProps"/>
</template> </template>
<template #item="{ value, weekStartDate, top }"> <template #item="{ value, weekStartDate, top }">
<meal-plan-calendar-item :item-height="calendarItemHeight" :value="value" :item-top="top" @onDragStart="currentlyDraggedMealplan = value" :detailed-items="lgAndUp"></meal-plan-calendar-item> <meal-plan-calendar-item
:item-height="calendarItemHeight"
:value="value"
:item-top="top"
@onDragStart="currentlyDraggedMealplan = value"
@delete="(arg: MealPlan) => {useMealPlanStore().plans.delete(arg.id)}"
:detailed-items="lgAndUp"
></meal-plan-calendar-item>
</template> </template>
</CalendarView> </CalendarView>
</v-col> </v-col>
@@ -37,8 +40,8 @@ import {computed, onMounted, ref} from "vue";
import {DateTime} from "luxon"; import {DateTime} from "luxon";
import {useDisplay} from "vuetify"; import {useDisplay} from "vuetify";
import {useMealPlanStore} from "@/stores/MealPlanStore"; import {useMealPlanStore} from "@/stores/MealPlanStore";
import MealPlanEditor from "@/components/model_editors/MealPlanEditor.vue";
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue"; import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
import {MealPlan} from "@/openapi";
const {lgAndUp} = useDisplay() const {lgAndUp} = useDisplay()

View File

@@ -13,10 +13,10 @@
<v-row> <v-row>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
<v-text-field label="Title" v-model="editingObj.title"></v-text-field> <v-text-field :label="$t('Title')" v-model="editingObj.title"></v-text-field>
<v-date-input <v-date-input
v-model="dateRangeValue" v-model="dateRangeValue"
label="Plan Date" :label="$t('Date')"
multiple="range" multiple="range"
prepend-icon="" prepend-icon=""
prepend-inner-icon="$calendar" prepend-inner-icon="$calendar"
@@ -32,7 +32,7 @@
</v-input> </v-input>
<ModelSelect model="MealType" :allow-create="true" v-model="editingObj.mealType"></ModelSelect> <ModelSelect model="MealType" :allow-create="true" v-model="editingObj.mealType"></ModelSelect>
<v-number-input control-variant="split" :min="0" v-model="editingObj.servings" label="Servings"></v-number-input> <v-number-input control-variant="split" :min="0" v-model="editingObj.servings" :label="$t('Servings')"></v-number-input>
<ModelSelect model="User" :allow-create="false" v-model="editingObj.shared" item-label="displayName" mode="tags"></ModelSelect> <ModelSelect model="User" :allow-create="false" v-model="editingObj.shared" item-label="displayName" mode="tags"></ModelSelect>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col cols="12" md="6">
@@ -45,7 +45,7 @@
</v-row> </v-row>
<v-row> <v-row>
<v-col> <v-col>
<v-textarea label="Note" v-model="editingObj.note"></v-textarea> <v-textarea :label="$t('Note')" v-model="editingObj.note"></v-textarea>
</v-col> </v-col>
</v-row> </v-row>
@@ -104,7 +104,14 @@ onMounted(() => {
// initialize date range slider // initialize date range slider
dateRangeValue.value.push(editingObj.value.fromDate) dateRangeValue.value.push(editingObj.value.fromDate)
}, () => { }, () => {
// TODO add all dates between start and end to date range dateRangeValue.value.push(editingObj.value.fromDate)
if(editingObj.value.toDate && editingObj.value.toDate != editingObj.value.fromDate) {
let currentDate = DateTime.fromJSDate(editingObj.value.fromDate).plus({day: 1}).toJSDate()
while(currentDate <= editingObj.value.toDate){
dateRangeValue.value.push(currentDate)
currentDate = DateTime.fromJSDate(currentDate).plus({day: 1}).toJSDate()
}
}
}) })
}) })
@@ -119,7 +126,7 @@ function updateDate() {
editingObj.value.fromDate = dateRangeValue.value[0] editingObj.value.fromDate = dateRangeValue.value[0]
editingObj.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1] editingObj.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
} else { } else {
useMessageStore().addMessage(MessageType.WARNING, 'Food', 7000) useMessageStore().addMessage(MessageType.WARNING, 'Missing Date', 7000)
return return
} }
} }

View File

@@ -98,7 +98,7 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
let name = '' let name = ''
if (editingObj.value.id) { if (editingObj.value.id) {
modelClass.value.model.toStringKeys.forEach(key => { modelClass.value.model.toStringKeys.forEach(key => {
name += ' ' + editingObj.value[key] name += ' ' + key.split('.').reduce((a, b) => a[b], editingObj.value);
}) })
} }
@@ -143,7 +143,7 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
*/ */
function deleteObject() { function deleteObject() {
modelClass.value.destroy(editingObj.value.id).then((r: any) => { modelClass.value.destroy(editingObj.value.id).then((r: any) => {
emit('delete', editingObj) emit('delete', editingObj.value)
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

@@ -32,15 +32,12 @@ import RecipeCardComponent from "@/components/display/RecipeCard.vue"
import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue" import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
import RecipeCard from "@/components/display/RecipeCard.vue" import RecipeCard from "@/components/display/RecipeCard.vue"
import HorizontalRecipeScroller from "@/components/display/HorizontalRecipeWindow.vue" import HorizontalRecipeScroller from "@/components/display/HorizontalRecipeWindow.vue"
import {DateTime} from "luxon"
import {useMealPlanStore} from "@/stores/MealPlanStore"
import HorizontalMealPlanWindow from "@/components/display/HorizontalMealPlanWindow.vue" import HorizontalMealPlanWindow from "@/components/display/HorizontalMealPlanWindow.vue"
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue"
import ModelSelect from "@/components/inputs/ModelSelect.vue" import ModelSelect from "@/components/inputs/ModelSelect.vue"
export default defineComponent({ export default defineComponent({
name: "StartPage", name: "StartPage",
components: {ModelSelect, MealPlanDialog, HorizontalMealPlanWindow, HorizontalRecipeScroller, RecipeCard, GlobalSearchDialog, RecipeCardComponent, KeywordsComponent}, components: {ModelSelect, HorizontalMealPlanWindow, HorizontalRecipeScroller, RecipeCard, GlobalSearchDialog, RecipeCardComponent, KeywordsComponent},
computed: {}, computed: {},
data() { data() {
return { return {

View File

@@ -156,7 +156,7 @@ export const TMealPlan = {
icon: 'fa-solid fa-calendar-days', icon: 'fa-solid fa-calendar-days',
isPaginated: true, isPaginated: true,
toStringKeys: ['name'], toStringKeys: ['title','recipe.name'],
tableHeaders: [ tableHeaders: [
{title: 'Title', key: 'title'}, {title: 'Title', key: 'title'},
@@ -404,6 +404,7 @@ export class GenericModel {
api: Object api: Object
model: Model model: Model
// TODO find out the type of the t useI18n object and use it here // TODO find out the type of the t useI18n object and use it here
// TODO decouple context from Generic model so t does not need to be passed
t: any t: any
/** /**