meal plan and recipe editor improvements

This commit is contained in:
vabene1111
2024-12-28 12:55:20 +01:00
parent ea1e47e579
commit cde632241b
42 changed files with 358 additions and 70 deletions

View File

@@ -0,0 +1,59 @@
<template>
<v-alert :title="props.title" closable @click:close="closeAlert()" v-if="showAlert">
<template #prepend>
<v-icon icon="$help"></v-icon>
</template>
<p>
{{ props.text}}
<v-btn color="success" class="float-right" v-if="props.actionText != ''" @click="emit('click')">{{ actionText}}</v-btn>
</p>
</v-alert>
</template>
<script setup lang="ts">
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import {MessageType, useMessageStore} from "@/stores/MessageStore";
import {computed} from "vue";
// emit click if action is clicked, actual effect must come from parent component
const emit = defineEmits(['click'])
const props = defineProps({
title: {type: String, required: false,},
text: {type: String, required: true},
// show an action button if any text is given and emit click event if button is pressed
actionText: {type: String, required: false,},
})
/**
* somewhat unique hash of the given text to save which alerts have already been closed
*/
const alertHash = computed(() => {
return props.text.split('').reduce((prevHash, currVal) => (((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0).toString()
})
/**
* only show the alert if it hasn't been closed on that device before
*/
const showAlert = computed(() => {
return !useUserPreferenceStore().deviceSettings.general_closedHelpAlerts.includes(alertHash.value)
})
/**
* called when alert is closed to save this alert into the list of closed alerts
*/
function closeAlert() {
if (!useUserPreferenceStore().deviceSettings.general_closedHelpAlerts.includes(alertHash.value)) {
useUserPreferenceStore().deviceSettings.general_closedHelpAlerts.push(alertHash.value)
} else {
useMessageStore().addMessage(MessageType.ERROR, 'Trying to close already closed alert', 0, props.text)
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,22 @@
<template>
<span v-if="ingredient.amount && !Number.isNaN(ingredient.amount)">{{$n(ingredient.amount)}}</span>
<span class="ms-1" v-if="ingredient.unit">{{ ingredient.unit.name}}</span>
<span class="ms-1" v-if="ingredient.food">{{ ingredient.food.name}}</span>
</template>
<script setup lang="ts">
import {Ingredient} from "@/openapi";
import {PropType} from "vue";
const props = defineProps({
ingredient: {type: {} as PropType<Ingredient>, required: true}
})
</script>
<style scoped>
</style>

View File

@@ -1,34 +1,38 @@
<template>
<v-row class="h-100">
<v-col>
<!-- TODO add hint about CTRL key while drag/drop -->
<calendar-view
:show-date="calendarDate"
:items="planItems"
class="theme-default"
:item-content-height="calendarItemHeight"
:enable-drag-drop="true"
@dropOnDate="dropCalendarItemOnDate"
:display-period-uom="useUserPreferenceStore().deviceSettings.mealplan_displayPeriod"
:display-period-count="useUserPreferenceStore().deviceSettings.mealplan_displayPeriodCount"
:starting-day-of-week="useUserPreferenceStore().deviceSettings.mealplan_startingDayOfWeek"
:display-week-numbers="useUserPreferenceStore().deviceSettings.mealplan_displayWeekNumbers"
:current-period-label="$t('Today')"
@click-date="(date : Date, calendarItems: [], windowEvent: any) => { newPlanDialogDefaultItem.fromDate = date; newPlanDialogDefaultItem.toDate = date; newPlanDialog = true }">
<template #header="{ headerProps }">
<calendar-view-header :header-props="headerProps" @input="(d:Date) => calendarDate = d"></calendar-view-header>
</template>
<template #item="{ value, weekStartDate, top }">
<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>
</calendar-view>
<v-col class="pb-0">
<v-card class="h-100" :loading="useMealPlanStore().loading">
<!-- TODO add hint about CTRL key while drag/drop -->
<!-- TODO multi selection? date range selection ? -->
<calendar-view
:show-date="calendarDate"
:items="planItems"
class="theme-default"
:item-content-height="calendarItemHeight"
:enable-drag-drop="true"
@dropOnDate="dropCalendarItemOnDate"
:display-period-uom="useUserPreferenceStore().deviceSettings.mealplan_displayPeriod"
:display-period-count="useUserPreferenceStore().deviceSettings.mealplan_displayPeriodCount"
:starting-day-of-week="useUserPreferenceStore().deviceSettings.mealplan_startingDayOfWeek"
:display-week-numbers="useUserPreferenceStore().deviceSettings.mealplan_displayWeekNumbers"
:current-period-label="$t('Today')"
@click-date="(date : Date, calendarItems: [], windowEvent: any) => { newPlanDialogDefaultItem.fromDate = date; newPlanDialogDefaultItem.toDate = date; newPlanDialog = true }">
<template #header="{ headerProps }">
<calendar-view-header :header-props="headerProps" @input="(d:Date) => calendarDate = d"></calendar-view-header>
</template>
<template #item="{ value, weekStartDate, top }">
<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>
</calendar-view>
</v-card>
<model-edit-dialog model="MealPlan" v-model="newPlanDialog" :itemDefaults="newPlanDialogDefaultItem"
@create="(arg: any) => useMealPlanStore().plans.set(arg.id, arg)"></model-edit-dialog>
@@ -44,8 +48,8 @@ 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 {DateTime} from "luxon";
import {computed, onMounted, ref, watch} from "vue";
import {DateTime, Duration} from "luxon";
import {useDisplay} from "vuetify";
import {useMealPlanStore} from "@/stores/MealPlanStore";
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
@@ -89,22 +93,47 @@ const calendarItemHeight = computed(() => {
}
})
onMounted(() => {
useMealPlanStore().refreshFromAPI() //TODO filter to visible date
/**
* watch calendar date and load entries accordingly
*/
watch(calendarDate, () => {
let daysInPeriod = 7
if (useUserPreferenceStore().deviceSettings.mealplan_displayPeriod == 'month') {
daysInPeriod = 31
} else if (useUserPreferenceStore().deviceSettings.mealplan_displayPeriod == 'year') {
daysInPeriod = 365
}
let days = useUserPreferenceStore().deviceSettings.mealplan_displayPeriodCount * daysInPeriod
useMealPlanStore().refreshFromAPI(calendarDate.value, DateTime.now().plus({days: days}).toJSDate())
})
onMounted(() => {
// initial load for next 30 days
useMealPlanStore().refreshFromAPI(calendarDate.value, DateTime.now().plus({days: 30}).toJSDate())
})
/**
* handle drop event for calendar items on fields
* @param undefinedItem
* @param targetDate
* @param event
*/
function dropCalendarItemOnDate(undefinedItem: IMealPlanNormalizedCalendarItem, targetDate: Date, event: DragEvent) {
//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)
if (mealPlan != undefined) {
let fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days')
let fromToDiff = {days: 1}
if (mealPlan.toDate) {
fromToDiff = DateTime.fromJSDate(mealPlan.toDate).diff(DateTime.fromJSDate(mealPlan.fromDate), 'days')
}
// create copy of item if control is pressed
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()