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 cols="12" md="6">
|
||||
<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>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -37,7 +37,7 @@
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions>
|
||||
<v-btn color="error">
|
||||
<v-btn color="error" @click="useMealPlanStore().deleteObject(mutableMealPlan); dialog = false">
|
||||
Delete
|
||||
</v-btn>
|
||||
<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 {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 Multiselect from '@vueform/multiselect'
|
||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||
import {useMessageStore} from "@/stores/MessageStore";
|
||||
|
||||
@@ -78,6 +77,8 @@ if (props.mealPlan != undefined) {
|
||||
watchEffect(() => {
|
||||
if (props.mealPlan != undefined) {
|
||||
mutableMealPlan.value = props.mealPlan
|
||||
dateRangeValue.value.push(mutableMealPlan.value.fromDate)
|
||||
dateRangeValue.value.push(mutableMealPlan.value.toDate)
|
||||
} else {
|
||||
mutableMealPlan.value = newMealPlan()
|
||||
}
|
||||
@@ -89,7 +90,7 @@ function saveMealPlan() {
|
||||
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]
|
||||
mutableMealPlan.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
|
||||
} else {
|
||||
useMessageStore().addError('Missing Dates')
|
||||
return
|
||||
@@ -108,6 +109,7 @@ function newMealPlan() {
|
||||
return {
|
||||
fromDate: DateTime.now().toJSDate(),
|
||||
toDate: DateTime.now().toJSDate(),
|
||||
servings: 1,
|
||||
} as MealPlan
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<meal-plan-dialog :meal-plan="mealPlan"></meal-plan-dialog>
|
||||
</v-card-text>
|
||||
|
||||
</v-card>
|
||||
@@ -28,6 +28,7 @@
|
||||
import {computed, PropType} from "vue";
|
||||
import {IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
||||
import RecipeImage from "@/components/display/RecipeImage.vue";
|
||||
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue";
|
||||
|
||||
const emit = defineEmits({
|
||||
onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<v-row class="h-100">
|
||||
<v-col>
|
||||
<!-- TODO add hint about CTRL key while drag/drop -->
|
||||
<CalendarView
|
||||
:items="planItems"
|
||||
class="theme-default"
|
||||
@@ -28,16 +29,18 @@ import "vue-simple-calendar/dist/css/default.css"
|
||||
import MealPlanCalendarItem from "@/components/display/MealPlanCalendarItem.vue";
|
||||
import {IMealPlanCalendarItem, IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {ApiApi, MealPlan} from "@/openapi";
|
||||
import {DateTime} from "luxon";
|
||||
import {useDisplay} from "vuetify";
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
|
||||
const {lgAndUp} = useDisplay()
|
||||
|
||||
const mealPlans = ref([] as MealPlan[])
|
||||
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(() => {
|
||||
let items = [] as IMealPlanCalendarItem[]
|
||||
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)
|
||||
if (currentlyDraggedMealplan.value.originalItem.mealPlan.id != undefined) {
|
||||
let mealPlan = useMealPlanStore().plans.get(currentlyDraggedMealplan.value.originalItem.mealPlan.id)
|
||||
let fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days')
|
||||
if (event.ctrlKey) {
|
||||
let new_entry = Object.assign({}, mealPlan)
|
||||
new_entry.fromDate = targetDate
|
||||
new_entry.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||
useMealPlanStore().createObject(new_entry)
|
||||
if (mealPlan != undefined) {
|
||||
let fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days')
|
||||
if (event.ctrlKey) {
|
||||
let new_entry = Object.assign({}, mealPlan)
|
||||
new_entry.fromDate = targetDate
|
||||
new_entry.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||
useMealPlanStore().createObject(new_entry)
|
||||
|
||||
} else {
|
||||
mealPlan.fromDate = targetDate
|
||||
mealPlan.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||
console.log('UPDAAAATING: ', mealPlan.fromDate, mealPlan.toDate)
|
||||
useMealPlanStore().updateObject(mealPlan)
|
||||
} else {
|
||||
mealPlan.fromDate = targetDate
|
||||
mealPlan.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||
useMealPlanStore().updateObject(mealPlan)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,39 @@
|
||||
<template>
|
||||
<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
|
||||
|
||||
:id="id"
|
||||
class="material-multiselect z-max"
|
||||
:resolve-on-load="false"
|
||||
v-model="model"
|
||||
:options="search"
|
||||
:delay="300"
|
||||
:object="true"
|
||||
valueProp="id"
|
||||
:label="label"
|
||||
:valueProp="itemValue"
|
||||
:label="itemLabel"
|
||||
:searchable="true"
|
||||
:strict="false"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, onMounted, PropType, ref, Ref} from "vue"
|
||||
import {ApiApi} from "@/openapi/index.js"
|
||||
import {onMounted, PropType, ref, Ref} from "vue"
|
||||
import {useDebounceFn} from "@vueuse/core"
|
||||
import {GenericModel, getModelFromStr} from "@/types/Models"
|
||||
import Multiselect from '@vueform/multiselect'
|
||||
import {ErrorMessageType, MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
@@ -61,22 +42,22 @@ const props = defineProps({
|
||||
|
||||
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
|
||||
|
||||
multiple: {type: Boolean, default: true},
|
||||
limit: {type: Number, default: 25},
|
||||
allowCreate: {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},
|
||||
label: {type: String, default: "name"},
|
||||
parent_variable: {type: String, default: undefined},
|
||||
|
||||
sticky_options: {
|
||||
@@ -85,42 +66,15 @@ const props = defineProps({
|
||||
return []
|
||||
},
|
||||
},
|
||||
initial_selection: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
initial_single_selection: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
|
||||
disabled: {type: Boolean, default: false},
|
||||
|
||||
})
|
||||
|
||||
const model = defineModel()
|
||||
const model_class = ref({} as GenericModel<any>)
|
||||
const items: Ref<Array<any>> = ref([])
|
||||
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
|
||||
@@ -130,36 +84,33 @@ function search(query: string) {
|
||||
return model_class.value.list(query).then((r) => {
|
||||
return r
|
||||
}).catch((err) => {
|
||||
//useMessageStore().addMessage(MessageType.ERROR, err, 8000)
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
search_loading.value = false
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// TODO refactor for new multiselect
|
||||
function addItem(item: string) {
|
||||
console.log("CREATEING NEW with -> ", item)
|
||||
const api = new ApiApi()
|
||||
api.apiKeywordList()
|
||||
|
||||
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) {
|
||||
selected_items.value.push(createdObj)
|
||||
} else {
|
||||
selected_items.value = createdObj
|
||||
}
|
||||
items.value.push(createdObj)
|
||||
selectionChanged()
|
||||
|
||||
}).catch((err) => {
|
||||
//StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE)
|
||||
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
|
||||
}).finally(() => {
|
||||
search_loading.value = false
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function selectionChanged() {
|
||||
emit('update:modelValue', selected_items)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style src="@vueform/multiselect/themes/default.css"></style>
|
||||
|
||||
Reference in New Issue
Block a user