mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 12:18:45 -05:00
meal plan stuff
This commit is contained in:
@@ -24,7 +24,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<ModelSelect model="recipe" v-model="mutableMealPlan.recipe"></ModelSelect>
|
<ModelSelect model="recipe" v-model="mutableMealPlan.recipe"></ModelSelect>
|
||||||
<!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>--> <!--TODO create days input with +/- snyced to date -->
|
<!-- <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>
|
<recipe-card :recipe="mutableMealPlan.recipe" v-if="mutableMealPlan && mutableMealPlan.recipe"></recipe-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="error">
|
<v-btn color="error" @click="useMealPlanStore().deleteObject(mutableMealPlan); dialog = false">
|
||||||
Delete
|
Delete
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="success" class="ml-auto" @click="saveMealPlan">
|
<v-btn color="success" class="ml-auto" @click="saveMealPlan">
|
||||||
@@ -57,7 +57,6 @@ import RecipeCard from "@/components/display/RecipeCard.vue";
|
|||||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||||
import {VNumberInput} from 'vuetify/labs/VNumberInput' //TODO remove once component is out of labs
|
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 {VDateInput} from 'vuetify/labs/VDateInput' //TODO remove once component is out of labs
|
||||||
import Multiselect from '@vueform/multiselect'
|
|
||||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||||
import {useMessageStore} from "@/stores/MessageStore";
|
import {useMessageStore} from "@/stores/MessageStore";
|
||||||
|
|
||||||
@@ -78,6 +77,8 @@ if (props.mealPlan != undefined) {
|
|||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (props.mealPlan != undefined) {
|
if (props.mealPlan != undefined) {
|
||||||
mutableMealPlan.value = props.mealPlan
|
mutableMealPlan.value = props.mealPlan
|
||||||
|
dateRangeValue.value.push(mutableMealPlan.value.fromDate)
|
||||||
|
dateRangeValue.value.push(mutableMealPlan.value.toDate)
|
||||||
} else {
|
} else {
|
||||||
mutableMealPlan.value = newMealPlan()
|
mutableMealPlan.value = newMealPlan()
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ function saveMealPlan() {
|
|||||||
mutableMealPlan.value.recipe = mutableMealPlan.value.recipe as RecipeOverview
|
mutableMealPlan.value.recipe = mutableMealPlan.value.recipe as RecipeOverview
|
||||||
if (dateRangeValue.value != null) {
|
if (dateRangeValue.value != null) {
|
||||||
mutableMealPlan.value.fromDate = dateRangeValue.value[0]
|
mutableMealPlan.value.fromDate = dateRangeValue.value[0]
|
||||||
mutableMealPlan.value.toDate = dateRangeValue.value[dateRangeValue.value.length-1]
|
mutableMealPlan.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
|
||||||
} else {
|
} else {
|
||||||
useMessageStore().addError('Missing Dates')
|
useMessageStore().addError('Missing Dates')
|
||||||
return
|
return
|
||||||
@@ -108,6 +109,7 @@ function newMealPlan() {
|
|||||||
return {
|
return {
|
||||||
fromDate: DateTime.now().toJSDate(),
|
fromDate: DateTime.now().toJSDate(),
|
||||||
toDate: DateTime.now().toJSDate(),
|
toDate: DateTime.now().toJSDate(),
|
||||||
|
servings: 1,
|
||||||
} as MealPlan
|
} as MealPlan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<meal-plan-dialog :meal-plan="mealPlan"></meal-plan-dialog>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
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";
|
||||||
|
|
||||||
const emit = defineEmits({
|
const emit = defineEmits({
|
||||||
onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => {
|
onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-row class="h-100">
|
<v-row class="h-100">
|
||||||
<v-col>
|
<v-col>
|
||||||
|
<!-- TODO add hint about CTRL key while drag/drop -->
|
||||||
<CalendarView
|
<CalendarView
|
||||||
:items="planItems"
|
:items="planItems"
|
||||||
class="theme-default"
|
class="theme-default"
|
||||||
@@ -28,16 +29,18 @@ import "vue-simple-calendar/dist/css/default.css"
|
|||||||
import MealPlanCalendarItem from "@/components/display/MealPlanCalendarItem.vue";
|
import MealPlanCalendarItem from "@/components/display/MealPlanCalendarItem.vue";
|
||||||
import {IMealPlanCalendarItem, IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
import {IMealPlanCalendarItem, IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
||||||
import {computed, onMounted, ref} from "vue";
|
import {computed, onMounted, ref} from "vue";
|
||||||
import {ApiApi, MealPlan} from "@/openapi";
|
|
||||||
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";
|
||||||
|
|
||||||
const {lgAndUp} = useDisplay()
|
const {lgAndUp} = useDisplay()
|
||||||
|
|
||||||
const mealPlans = ref([] as MealPlan[])
|
|
||||||
const currentlyDraggedMealplan = ref({} as IMealPlanNormalizedCalendarItem)
|
const currentlyDraggedMealplan = ref({} as IMealPlanNormalizedCalendarItem)
|
||||||
|
const clickedMealPlan = ref({} as IMealPlanNormalizedCalendarItem)
|
||||||
|
/**
|
||||||
|
* computed property that converts array of MealPlan object to
|
||||||
|
* array of CalendarItems (format required/extended from vue-simple-calendar)
|
||||||
|
*/
|
||||||
const planItems = computed(() => {
|
const planItems = computed(() => {
|
||||||
let items = [] as IMealPlanCalendarItem[]
|
let items = [] as IMealPlanCalendarItem[]
|
||||||
useMealPlanStore().planList.forEach(mp => {
|
useMealPlanStore().planList.forEach(mp => {
|
||||||
@@ -67,18 +70,19 @@ function dropCalendarItemOnDate(undefinedItem: IMealPlanNormalizedCalendarItem,
|
|||||||
//The item argument (first) is undefined because our custom calendar item cannot manipulate the calendar state so the item is unknown to the calendar (probably fixable by somehow binding state to the item)
|
//The item argument (first) is undefined because our custom calendar item cannot manipulate the calendar state so the item is unknown to the calendar (probably fixable by somehow binding state to the item)
|
||||||
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)
|
||||||
let fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days')
|
if (mealPlan != undefined) {
|
||||||
if (event.ctrlKey) {
|
let fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days')
|
||||||
let new_entry = Object.assign({}, mealPlan)
|
if (event.ctrlKey) {
|
||||||
new_entry.fromDate = targetDate
|
let new_entry = Object.assign({}, mealPlan)
|
||||||
new_entry.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
new_entry.fromDate = targetDate
|
||||||
useMealPlanStore().createObject(new_entry)
|
new_entry.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||||
|
useMealPlanStore().createObject(new_entry)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
mealPlan.fromDate = targetDate
|
mealPlan.fromDate = targetDate
|
||||||
mealPlan.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
mealPlan.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||||
console.log('UPDAAAATING: ', mealPlan.fromDate, mealPlan.toDate)
|
useMealPlanStore().updateObject(mealPlan)
|
||||||
useMealPlanStore().updateObject(mealPlan)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-input>
|
<v-input>
|
||||||
<!-- <!–TODO Problems: 1. behind other cards when those are underneath the element, making card overflow visible breaks cards –>-->
|
|
||||||
<!-- <VueMultiselect-->
|
|
||||||
<!-- :id="id"-->
|
|
||||||
<!-- v-model="selected_items"-->
|
|
||||||
<!-- :options="items"-->
|
|
||||||
<!-- :close-on-select="true"-->
|
|
||||||
<!-- :clear-on-select="true"-->
|
|
||||||
<!-- :hide-selected="multiple"-->
|
|
||||||
<!-- :preserve-search="true"-->
|
|
||||||
<!-- :internal-search="false"-->
|
|
||||||
<!-- :limit="limit"-->
|
|
||||||
<!-- :placeholder="model"-->
|
|
||||||
<!-- :label="label"-->
|
|
||||||
<!-- track-by="id"-->
|
|
||||||
<!-- :multiple="multiple"-->
|
|
||||||
<!-- :taggable="allowCreate"-->
|
|
||||||
<!-- tag-placeholder="TODO CREATE PLACEHOLDER"-->
|
|
||||||
<!-- :loading="search_loading"-->
|
|
||||||
<!-- @search-change="debouncedSearchFunction"-->
|
|
||||||
<!-- @input="selectionChanged"-->
|
|
||||||
<!-- @tag="addItem"-->
|
|
||||||
<!-- @open="search('')"-->
|
|
||||||
<!-- :disabled="disabled"-->
|
|
||||||
<!-- class="material-multiselect"-->
|
|
||||||
|
|
||||||
<!-- >-->
|
|
||||||
<!-- </VueMultiselect>-->
|
|
||||||
|
|
||||||
|
<!--TODO resolve-on-load false for now, race condition with model class, make prop once better solution is found -->
|
||||||
|
<!-- TODO strange behavior/layering issues with appendTo body, find soltion to make it work -->
|
||||||
<Multiselect
|
<Multiselect
|
||||||
|
:id="id"
|
||||||
class="material-multiselect z-max"
|
class="material-multiselect z-max"
|
||||||
|
:resolve-on-load="false"
|
||||||
v-model="model"
|
v-model="model"
|
||||||
:options="search"
|
:options="search"
|
||||||
:delay="300"
|
:delay="300"
|
||||||
:object="true"
|
:object="true"
|
||||||
valueProp="id"
|
:valueProp="itemValue"
|
||||||
:label="label"
|
:label="itemLabel"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:strict="false"
|
:strict="false"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:mode="mode"
|
||||||
|
:can-clear="canClear"
|
||||||
|
:can-deselect="canClear"
|
||||||
|
:limit="limit"
|
||||||
|
placeholder="TODO ADD LOCALIZED PLACEHOLDER"
|
||||||
|
noOptionsText="TODO ADD LOCALIZED NO-OPTIONS"
|
||||||
|
noResultsText="TODO ADD LOCALIZED NO-RESULTS"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</v-input>
|
</v-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {computed, onMounted, PropType, ref, Ref} from "vue"
|
import {onMounted, PropType, ref, Ref} from "vue"
|
||||||
import {ApiApi} from "@/openapi/index.js"
|
|
||||||
import {useDebounceFn} from "@vueuse/core"
|
import {useDebounceFn} from "@vueuse/core"
|
||||||
import {GenericModel, getModelFromStr} from "@/types/Models"
|
import {GenericModel, getModelFromStr} from "@/types/Models"
|
||||||
import Multiselect from '@vueform/multiselect'
|
import Multiselect from '@vueform/multiselect'
|
||||||
|
import {ErrorMessageType, MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
@@ -61,22 +42,22 @@ const props = defineProps({
|
|||||||
|
|
||||||
id: {type: String, required: false, default: Math.random().toString()},
|
id: {type: String, required: false, default: Math.random().toString()},
|
||||||
|
|
||||||
|
itemLabel: {type: String, default: "name"},
|
||||||
|
itemValue: {type: String, default: "id"},
|
||||||
|
limit: {type: Number, default: 25},
|
||||||
|
|
||||||
|
disabled: {type: Boolean, default: false},
|
||||||
|
canClear: {type: Boolean, default: true},
|
||||||
|
|
||||||
|
mode: {type: String as PropType<'single' | 'multiple' | 'tags'>, default: 'single'},
|
||||||
|
|
||||||
// not verified
|
// not verified
|
||||||
|
|
||||||
multiple: {type: Boolean, default: true},
|
|
||||||
limit: {type: Number, default: 25},
|
|
||||||
allowCreate: {type: Boolean, default: false},
|
allowCreate: {type: Boolean, default: false},
|
||||||
|
|
||||||
search_on_load: {type: Boolean, default: false},
|
search_on_load: {type: Boolean, default: false},
|
||||||
|
|
||||||
clearable: {type: Boolean, default: false},
|
|
||||||
chips: {type: Boolean, default: undefined},
|
|
||||||
|
|
||||||
itemName: {type: String, default: "name"},
|
|
||||||
itemValue: {type: String, default: "id"},
|
|
||||||
|
|
||||||
placeholder: {type: String, default: undefined},
|
placeholder: {type: String, default: undefined},
|
||||||
label: {type: String, default: "name"},
|
|
||||||
parent_variable: {type: String, default: undefined},
|
parent_variable: {type: String, default: undefined},
|
||||||
|
|
||||||
sticky_options: {
|
sticky_options: {
|
||||||
@@ -85,42 +66,15 @@ const props = defineProps({
|
|||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
initial_selection: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
},
|
|
||||||
initial_single_selection: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
|
|
||||||
disabled: {type: Boolean, default: false},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const model = defineModel()
|
const model = defineModel()
|
||||||
const model_class = ref({} as GenericModel<any>)
|
const model_class = ref({} as GenericModel<any>)
|
||||||
const items: Ref<Array<any>> = ref([])
|
const items: Ref<Array<any>> = ref([])
|
||||||
const selected_items: Ref<Array<any> | any> = ref(undefined)
|
const selected_items: Ref<Array<any> | any> = ref(undefined)
|
||||||
const search_query = ref("")
|
|
||||||
const search_loading = ref(false)
|
|
||||||
|
|
||||||
const elementId = ref((Math.random() * 100000).toString())
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
model_class.value = getModelFromStr(props.model)
|
|
||||||
if (props.search_on_load) {
|
|
||||||
debouncedSearchFunction("")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* debounced search function bound to search input changing
|
|
||||||
*/
|
|
||||||
const debouncedSearchFunction = useDebounceFn((query: string) => {
|
|
||||||
search(query)
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* performs the API request to search for the selected input
|
* performs the API request to search for the selected input
|
||||||
@@ -130,36 +84,33 @@ function search(query: string) {
|
|||||||
return model_class.value.list(query).then((r) => {
|
return model_class.value.list(query).then((r) => {
|
||||||
return r
|
return r
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
//useMessageStore().addMessage(MessageType.ERROR, err, 8000)
|
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
search_loading.value = false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO refactor for new multiselect
|
||||||
function addItem(item: string) {
|
function addItem(item: string) {
|
||||||
console.log("CREATEING NEW with -> ", item)
|
console.log("CREATEING NEW with -> ", item)
|
||||||
const api = new ApiApi()
|
|
||||||
api.apiKeywordList()
|
|
||||||
|
|
||||||
model_class.value.create(item).then((createdObj) => {
|
model_class.value.create(item).then((createdObj) => {
|
||||||
//StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
|
useMessageStore().addMessage(MessageType.SUCCESS, 'Created', 5000)
|
||||||
if (selected_items.value instanceof Array) {
|
if (selected_items.value instanceof Array) {
|
||||||
selected_items.value.push(createdObj)
|
selected_items.value.push(createdObj)
|
||||||
} else {
|
} else {
|
||||||
selected_items.value = createdObj
|
selected_items.value = createdObj
|
||||||
}
|
}
|
||||||
items.value.push(createdObj)
|
items.value.push(createdObj)
|
||||||
selectionChanged()
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
//StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE)
|
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
search_loading.value = false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectionChanged() {
|
|
||||||
emit('update:modelValue', selected_items)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="@vueform/multiselect/themes/default.css"></style>
|
<style src="@vueform/multiselect/themes/default.css"></style>
|
||||||
|
|||||||
Reference in New Issue
Block a user