mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 12:18:45 -05:00
meal plan and model editors
- changed signature to options object - added ability to set defaults - meal plan clickable item creation
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<v-dialog max-width="1400" :activator="activator" v-model="dialog">
|
||||
<component :is="editorComponent" :item="item" @create="createEvent" @save="saveEvent" @delete="deleteEvent" dialog @close="dialog = false"></component>
|
||||
<v-dialog max-width="1400" :activator="dialogActivator" v-model="model">
|
||||
<component :is="editorComponent" :item="item" @create="createEvent" @save="saveEvent" @delete="deleteEvent" dialog @close="dialog = false" :itemDefaults="itemDefaults"></component>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
import {defineAsyncComponent, PropType, ref, shallowRef, watch} from "vue";
|
||||
import {defineAsyncComponent, PropType, shallowRef, watch} from "vue";
|
||||
import {EditorSupportedModels, getGenericModelFromString} from "@/types/Models";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {MealPlan} from "@/openapi";
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
@@ -19,6 +20,7 @@ const props = defineProps({
|
||||
model: { type: String as PropType<EditorSupportedModels>, required: true, },
|
||||
activator: {default: 'parent'},
|
||||
item: {default: null},
|
||||
itemDefaults: {required: false},
|
||||
disabledFields: {default: []},
|
||||
closeAfterCreate: {default: true},
|
||||
closeAfterSave: {default: true},
|
||||
@@ -27,36 +29,41 @@ const props = defineProps({
|
||||
|
||||
const editorComponent = shallowRef(defineAsyncComponent(() => import(`@/components/model_editors/${getGenericModelFromString(props.model, t).model.name}Editor.vue`)))
|
||||
|
||||
const dialog = ref(false)
|
||||
const model = defineModel<Boolean>({default: false})
|
||||
const model = defineModel<Boolean|undefined>({default: undefined})
|
||||
const dialogActivator = (model.value !== undefined) ? undefined : props.activator
|
||||
|
||||
/**
|
||||
* Allow opening the model edit dialog trough v-model property of the dialog by watching for model changes
|
||||
*/
|
||||
watch(model, (value, oldValue, onCleanup) => {
|
||||
console.log('model changed to ', value)
|
||||
dialog.value = !!value
|
||||
})
|
||||
|
||||
watch(dialog, (value, oldValue, onCleanup) => {
|
||||
console.log('dialog changed to ', value)
|
||||
model.value = !!value
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* forward event to parent component and handle closing the editor if configured to do so
|
||||
* @param arg model object from editor component
|
||||
*/
|
||||
function createEvent(arg: any) {
|
||||
emit('create', arg)
|
||||
dialog.value = dialog.value && !props.closeAfterCreate
|
||||
model.value = model.value && !props.closeAfterCreate
|
||||
}
|
||||
|
||||
/**
|
||||
* forward event to parent component and handle closing the editor if configured to do so
|
||||
* @param arg model object from editor component
|
||||
*/
|
||||
function saveEvent(arg: any) {
|
||||
emit('save', arg)
|
||||
dialog.value = dialog.value && !props.closeAfterSave
|
||||
model.value = model.value && !props.closeAfterSave
|
||||
}
|
||||
|
||||
/**
|
||||
* forward event to parent component and handle closing the editor if configured to do so
|
||||
* @param arg model object from editor component
|
||||
*/
|
||||
function deleteEvent(arg: any) {
|
||||
emit('delete', arg)
|
||||
dialog.value = dialog.value && !props.closeAfterDelete
|
||||
model.value = model.value && !props.closeAfterDelete
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:item-content-height="calendarItemHeight"
|
||||
:enable-drag-drop="true"
|
||||
@dropOnDate="dropCalendarItemOnDate"
|
||||
@click-date="newPlanDialog = true">
|
||||
@click-date="(date : Date, calendarItems: [], windowEvent: any) => { newPlanDialogDefaultItem.fromDate = date; newPlanDialogDefaultItem.toDate = date; newPlanDialog = true }">
|
||||
<template #header="{ headerProps }">
|
||||
<CalendarViewHeader :header-props="headerProps"/>
|
||||
</template>
|
||||
@@ -24,21 +24,20 @@
|
||||
</template>
|
||||
</CalendarView>
|
||||
|
||||
<model-edit-dialog model="MealPlan" v-model="newPlanDialog"></model-edit-dialog>
|
||||
<model-edit-dialog model="MealPlan" v-model="newPlanDialog" :itemDefaults="newPlanDialogDefaultItem" @create="(arg: any) => useMealPlanStore().plans.set(arg.id, arg)"></model-edit-dialog>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {CalendarView, CalendarViewHeader} from "vue-simple-calendar"
|
||||
import "vue-simple-calendar/dist/style.css"
|
||||
import "vue-simple-calendar/dist/css/default.css"
|
||||
|
||||
import MealPlanCalendarItem from "@/components/display/MealPlanCalendarItem.vue";
|
||||
import {IMealPlanCalendarItem, IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
||||
import {computed, nextTick, onMounted, ref, useTemplateRef} from "vue";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {DateTime} from "luxon";
|
||||
import {useDisplay} from "vuetify";
|
||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||
@@ -50,6 +49,7 @@ const {lgAndUp} = useDisplay()
|
||||
const currentlyDraggedMealplan = ref({} as IMealPlanNormalizedCalendarItem)
|
||||
|
||||
const newPlanDialog = ref(false)
|
||||
const newPlanDialogDefaultItem = ref({} as MealPlan)
|
||||
|
||||
/**
|
||||
* computed property that converts array of MealPlan object to
|
||||
|
||||
@@ -49,9 +49,11 @@ const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading,
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
setupState(props.item, props.itemId, () => {
|
||||
editingObj.value.expires = DateTime.now().plus({year: 1}).toJSDate()
|
||||
editingObj.value.scope = 'read write'
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
editingObj.value.expires = DateTime.now().plus({year: 1}).toJSDate()
|
||||
editingObj.value.scope = 'read write'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -52,21 +52,23 @@ const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading,
|
||||
// object specific data (for selects/display)
|
||||
|
||||
const AUTOMATION_TYPES = [
|
||||
{ value: "FOOD_ALIAS", title: t("Food_Alias") },
|
||||
{ value: "UNIT_ALIAS", title: t("Unit_Alias") },
|
||||
{ value: "KEYWORD_ALIAS", title: t("Keyword_Alias") },
|
||||
{ value: "NAME_REPLACE", title: t("Name_Replace") },
|
||||
{ value: "DESCRIPTION_REPLACE", title: t("Description_Replace") },
|
||||
{ value: "INSTRUCTION_REPLACE", title: t("Instruction_Replace") },
|
||||
{ value: "FOOD_REPLACE", title: t("Food_Replace") },
|
||||
{ value: "UNIT_REPLACE", title: t("Unit_Replace") },
|
||||
{ value: "NEVER_UNIT", title: t("Never_Unit") },
|
||||
{ value: "TRANSPOSE_WORDS", title: t("Transpose_Words") }
|
||||
{value: "FOOD_ALIAS", title: t("Food_Alias")},
|
||||
{value: "UNIT_ALIAS", title: t("Unit_Alias")},
|
||||
{value: "KEYWORD_ALIAS", title: t("Keyword_Alias")},
|
||||
{value: "NAME_REPLACE", title: t("Name_Replace")},
|
||||
{value: "DESCRIPTION_REPLACE", title: t("Description_Replace")},
|
||||
{value: "INSTRUCTION_REPLACE", title: t("Instruction_Replace")},
|
||||
{value: "FOOD_REPLACE", title: t("Food_Replace")},
|
||||
{value: "UNIT_REPLACE", title: t("Unit_Replace")},
|
||||
{value: "NEVER_UNIT", title: t("Never_Unit")},
|
||||
{value: "TRANSPOSE_WORDS", title: t("Transpose_Words")}
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
setupState(props.item, props.itemId, () => {
|
||||
editingObj.value.order = 0
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
editingObj.value.order = 0
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -189,9 +189,11 @@ const stopConversionsWatcher = watch(tab, (value, oldValue, onCleanup) => {
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
setupState(props.item, props.itemId, () => {
|
||||
editingObj.value.propertiesFoodAmount = 100
|
||||
editingObj.value.propertiesFoodUnit = {name: 'g'} as Unit // TODO properly fetch default unit
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
editingObj.value.propertiesFoodAmount = 100
|
||||
editingObj.value.propertiesFoodUnit = {name: 'g'} as Unit // TODO properly fetch default unit
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -50,9 +50,11 @@ onMounted(() => {
|
||||
api.apiGroupList().then(r => {
|
||||
groups.value = r
|
||||
|
||||
setupState(props.item, props.itemId, () => {
|
||||
editingObj.value.validUntil = DateTime.now().plus({month: 1}).toJSDate()
|
||||
editingObj.value.group = groups.value[0]
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
editingObj.value.validUntil = DateTime.now().plus({month: 1}).toJSDate()
|
||||
editingObj.value.group = groups.value[0]
|
||||
}
|
||||
})
|
||||
|
||||
}).catch(err => {
|
||||
|
||||
@@ -73,12 +73,13 @@ import {MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
|
||||
const props = defineProps({
|
||||
item: {type: {} as PropType<MealPlan>, required: false, default: null},
|
||||
itemDefaults: {type: {} as PropType<MealPlan>, required: false, default: {} as MealPlan},
|
||||
itemId: {type: [Number, String], required: false, default: undefined},
|
||||
dialog: {type: Boolean, default: false}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['create', 'save', 'delete', 'close'])
|
||||
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading, editingObj, modelClass} = useModelEditorFunctions<MealPlan>('MealPlan', emit)
|
||||
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, applyItemDefaults, loading, editingObj, modelClass} = useModelEditorFunctions<MealPlan>('MealPlan', emit)
|
||||
|
||||
// object specific data (for selects/display)
|
||||
const dateRangeValue = ref([] as Date[])
|
||||
@@ -87,32 +88,35 @@ onMounted(() => {
|
||||
const api = new ApiApi()
|
||||
|
||||
api.apiMealTypeList().then(r => {
|
||||
|
||||
// TODO remove this once moved to user preference from MealType property
|
||||
let defaultMealType = {} as MealType
|
||||
r.results.forEach(r => {
|
||||
if (r._default) {
|
||||
defaultMealType = r
|
||||
}
|
||||
})
|
||||
if (Object.keys(defaultMealType).length == 0 && r.results.length > 0) {
|
||||
defaultMealType = r.results[0]
|
||||
}
|
||||
|
||||
setupState(props.item, props.itemId, () => {
|
||||
editingObj.value.fromDate = DateTime.now().toJSDate()
|
||||
editingObj.value.toDate = DateTime.now().toJSDate()
|
||||
editingObj.value.shared = useUserPreferenceStore().userSettings.planShare
|
||||
editingObj.value.servings = 1
|
||||
editingObj.value.mealType = defaultMealType
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
console.log('running new Item Function')
|
||||
editingObj.value.fromDate = DateTime.now().toJSDate()
|
||||
editingObj.value.toDate = DateTime.now().toJSDate()
|
||||
editingObj.value.shared = useUserPreferenceStore().userSettings.planShare
|
||||
editingObj.value.servings = 1
|
||||
editingObj.value.mealType = defaultMealType
|
||||
|
||||
// initialize date range slider
|
||||
dateRangeValue.value.push(editingObj.value.fromDate)
|
||||
}, () => {
|
||||
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()
|
||||
}
|
||||
applyItemDefaults(props.itemDefaults)
|
||||
|
||||
initializeDateRange()
|
||||
console.log(editingObj.value)
|
||||
}, existingItemFunction: () => {
|
||||
initializeDateRange()
|
||||
}
|
||||
})
|
||||
},)
|
||||
})
|
||||
|
||||
})
|
||||
@@ -131,6 +135,22 @@ function updateDate() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initialize the dateRange selector when the editingObject is initialized
|
||||
*/
|
||||
function initializeDateRange() {
|
||||
if (editingObj.value.toDate && DateTime.fromJSDate(editingObj.value.toDate).diff(DateTime.fromJSDate(editingObj.value.fromDate), 'days').toObject().days! >= 1) {
|
||||
dateRangeValue.value = [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()
|
||||
}
|
||||
} else {
|
||||
dateRangeValue.value = [editingObj.value.fromDate, editingObj.value.fromDate]
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -122,10 +122,12 @@ onMounted(() => {
|
||||
api.apiSupermarketCategoryList({pageSize: 100}).then(r => {
|
||||
supermarketCategories.value = r.results
|
||||
|
||||
setupState(props.item, props.itemId, undefined, () => {
|
||||
editingObj.value.categoryToSupermarket.forEach(cTS => {
|
||||
editingObjSupermarketCategories.value.push(cTS.category)
|
||||
})
|
||||
setupState(props.item, props.itemId, {
|
||||
existingItemFunction: () => {
|
||||
editingObj.value.categoryToSupermarket.forEach(cTS => {
|
||||
editingObjSupermarketCategories.value.push(cTS.category)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,6 +22,15 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
|
||||
modelClass.value = getGenericModelFromString(modelName, t)
|
||||
})
|
||||
|
||||
function applyItemDefaults(itemDefaults: T) {
|
||||
if (Object.keys(itemDefaults).length > 0) {
|
||||
Object.keys(itemDefaults).forEach(k => {
|
||||
console.log('applying default ', k)
|
||||
editingObj.value[k] = itemDefaults[k]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if given an item or itemId, sets up the editingObj with that item or loads the data from the API using the ID
|
||||
* once finished loading updates the loading state to false, indicating finished initialization
|
||||
@@ -29,23 +38,35 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
|
||||
* @throws Error if an error if neither item or itemId are given and create is disabled
|
||||
* @param item item object to set as editingObj
|
||||
* @param itemId id of object to be retrieved and set as editingObj
|
||||
* @param newItemFunction optional function to execute if no object is given (by either item or itemId)
|
||||
* @param existingItemFunction optional function to execute once the existing item was loaded (instantly with item, async with itemId)
|
||||
* @param options optional parameters
|
||||
* newItemFunction: called when no item is given. When overriding you must implement applyItemDefaults if you want them to be applied.
|
||||
* existingItemFunction: called when some kind of item is passed
|
||||
* @return promise resolving to either the editingObj or undefined if errored
|
||||
*/
|
||||
function setupState(item: T | null, itemId: number | string | undefined,
|
||||
newItemFunction: () => void = () => {
|
||||
},
|
||||
existingItemFunction: () => void = () => {
|
||||
}): Promise<T | undefined> {
|
||||
function setupState(item: T | null, itemId: number | string | undefined, options: {
|
||||
itemDefaults?: T,
|
||||
newItemFunction?: () => void,
|
||||
existingItemFunction?: () => void,
|
||||
} = {}
|
||||
): Promise<T | undefined> {
|
||||
|
||||
const {
|
||||
itemDefaults = {} as T,
|
||||
newItemFunction = () => {
|
||||
applyItemDefaults(itemDefaults)
|
||||
},
|
||||
existingItemFunction = () => {
|
||||
}
|
||||
} = options
|
||||
|
||||
if (item === null && (itemId === undefined || itemId == '')) {
|
||||
// neither item nor itemId given => new item
|
||||
|
||||
if (modelClass.value.model.disableCreate) {
|
||||
throw Error('Trying to use a ModelEditor without an item and a model that does not allow object creation!')
|
||||
}
|
||||
|
||||
newItemFunction()
|
||||
|
||||
loading.value = false
|
||||
return Promise.resolve(editingObj.value)
|
||||
} else if (item !== null) {
|
||||
@@ -72,11 +93,12 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
|
||||
} else {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}
|
||||
return undefined
|
||||
return Promise.resolve(undefined)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,5 +172,5 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
|
||||
})
|
||||
}
|
||||
|
||||
return {setupState, saveObject, deleteObject, isUpdate, editingObjName, loading, editingObj, modelClass}
|
||||
return {setupState, saveObject, deleteObject, isUpdate, editingObjName, applyItemDefaults, loading, editingObj, modelClass}
|
||||
}
|
||||
Reference in New Issue
Block a user