mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
meal plan editor tweaks
This commit is contained in:
@@ -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>
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="align-self-center">
|
||||
<v-btn variant="flat" icon="">
|
||||
<i class="fas fa-plus"></i>
|
||||
<meal-plan-dialog></meal-plan-dialog>
|
||||
<model-edit-dialog model="MealPlan"></model-edit-dialog>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
<v-list-item-subtitle>
|
||||
{{ p.mealType.name }}
|
||||
</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>
|
||||
@@ -55,14 +55,13 @@
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, onMounted, PropType, ref, toRefs} from 'vue'
|
||||
import RecipeCard from "@/components/display/RecipeCard.vue";
|
||||
import {computed, onMounted, ref} from 'vue'
|
||||
import {useDisplay} from "vuetify";
|
||||
import {MealPlan, Recipe, RecipeOverview} from "@/openapi";
|
||||
import {MealPlan} from "@/openapi";
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
import {DateTime} from "luxon";
|
||||
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue";
|
||||
import {homePageCols} from "@/utils/breakpoint_utils";
|
||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
</span>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@@ -28,12 +27,16 @@
|
||||
import {computed, PropType} from "vue";
|
||||
import {IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
||||
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({
|
||||
onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => {
|
||||
return true
|
||||
},
|
||||
delete: (value: MealPlan) => {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
let props = defineProps({
|
||||
|
||||
@@ -2,22 +2,25 @@
|
||||
<v-row class="h-100">
|
||||
<v-col>
|
||||
<!-- TODO add hint about CTRL key while drag/drop -->
|
||||
<v-btn>
|
||||
|
||||
<model-edit-dialog model="MealPlan"></model-edit-dialog>
|
||||
</v-btn>
|
||||
|
||||
<CalendarView
|
||||
:items="planItems"
|
||||
class="theme-default"
|
||||
:item-content-height="calendarItemHeight"
|
||||
:enable-drag-drop="true"
|
||||
@dropOnDate="dropCalendarItemOnDate">
|
||||
@dropOnDate="dropCalendarItemOnDate"
|
||||
@click-date="">
|
||||
<template #header="{ headerProps }">
|
||||
<CalendarViewHeader :header-props="headerProps"/>
|
||||
</template>
|
||||
<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>
|
||||
</CalendarView>
|
||||
</v-col>
|
||||
@@ -37,8 +40,8 @@ import {computed, onMounted, ref} from "vue";
|
||||
import {DateTime} from "luxon";
|
||||
import {useDisplay} from "vuetify";
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
import MealPlanEditor from "@/components/model_editors/MealPlanEditor.vue";
|
||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||
import {MealPlan} from "@/openapi";
|
||||
|
||||
const {lgAndUp} = useDisplay()
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
|
||||
<v-row>
|
||||
<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-model="dateRangeValue"
|
||||
label="Plan Date"
|
||||
:label="$t('Date')"
|
||||
multiple="range"
|
||||
prepend-icon=""
|
||||
prepend-inner-icon="$calendar"
|
||||
@@ -32,7 +32,7 @@
|
||||
</v-input>
|
||||
|
||||
<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>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
@@ -45,7 +45,7 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<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-row>
|
||||
|
||||
@@ -104,7 +104,14 @@ onMounted(() => {
|
||||
// initialize date range slider
|
||||
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.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
|
||||
} else {
|
||||
useMessageStore().addMessage(MessageType.WARNING, 'Food', 7000)
|
||||
useMessageStore().addMessage(MessageType.WARNING, 'Missing Date', 7000)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
|
||||
let name = ''
|
||||
if (editingObj.value.id) {
|
||||
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() {
|
||||
modelClass.value.destroy(editingObj.value.id).then((r: any) => {
|
||||
emit('delete', editingObj)
|
||||
emit('delete', editingObj.value)
|
||||
editingObj.value = {} as T
|
||||
}).catch((err: any) => {
|
||||
useMessageStore().addError(ErrorMessageType.DELETE_ERROR, err)
|
||||
|
||||
@@ -32,15 +32,12 @@ import RecipeCardComponent from "@/components/display/RecipeCard.vue"
|
||||
import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
|
||||
import RecipeCard from "@/components/display/RecipeCard.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 MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue"
|
||||
import ModelSelect from "@/components/inputs/ModelSelect.vue"
|
||||
|
||||
export default defineComponent({
|
||||
name: "StartPage",
|
||||
components: {ModelSelect, MealPlanDialog, HorizontalMealPlanWindow, HorizontalRecipeScroller, RecipeCard, GlobalSearchDialog, RecipeCardComponent, KeywordsComponent},
|
||||
components: {ModelSelect, HorizontalMealPlanWindow, HorizontalRecipeScroller, RecipeCard, GlobalSearchDialog, RecipeCardComponent, KeywordsComponent},
|
||||
computed: {},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -156,7 +156,7 @@ export const TMealPlan = {
|
||||
icon: 'fa-solid fa-calendar-days',
|
||||
|
||||
isPaginated: true,
|
||||
toStringKeys: ['name'],
|
||||
toStringKeys: ['title','recipe.name'],
|
||||
|
||||
tableHeaders: [
|
||||
{title: 'Title', key: 'title'},
|
||||
@@ -404,6 +404,7 @@ export class GenericModel {
|
||||
api: Object
|
||||
model: Model
|
||||
// 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
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user