mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-02 04:39:54 -05:00
basic shopping view in MealPlanEditor
This commit is contained in:
@@ -7,14 +7,13 @@
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
|
||||
<div class="flex-grow-1 p-2">
|
||||
<div class="d-flex">
|
||||
<div class="d-flex flex-column pr-2">
|
||||
<span v-for="[i, a] in amounts" v-bind:key="a.key">
|
||||
<span>
|
||||
<i class="fas fa-check text-success fa-fw" v-if="a.checked"></i>
|
||||
<i class="fas fa-clock-rotate-left text-info fa-fw" v-if="a.delayed"></i> <b>
|
||||
<i class="fas fa-clock-rotate-left text-info fa-fw" v-if="a.delayed"></i> <b>
|
||||
<span :class="{'text-disabled': a.checked || a.delayed}">
|
||||
{{ $n(a.amount) }}
|
||||
<span v-if="a.unit">{{ a.unit.name }}</span>
|
||||
@@ -25,7 +24,7 @@
|
||||
<br/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-grow-1 align-self-center" >
|
||||
<div class="d-flex flex-column flex-grow-1 align-self-center">
|
||||
{{ shoppingListFood.food.name }} <br/>
|
||||
<span v-if="infoRow"><small class="text-disabled">{{ infoRow }}</small></span>
|
||||
</div>
|
||||
@@ -62,8 +61,12 @@ import {isDelayed, isShoppingListFoodDelayed} from "@/utils/logic_utils";
|
||||
const emit = defineEmits(['clicked'])
|
||||
|
||||
const props = defineProps({
|
||||
entries: {type: Array as PropType<Array<ShoppingListEntry>>, required: true},
|
||||
shoppingListFood: {type: {} as PropType<IShoppingListFood>, required: true},
|
||||
hideInfoRow: {type: Boolean, default: false}
|
||||
})
|
||||
|
||||
const entries = computed(() => {
|
||||
return Array.from(props.shoppingListFood.entries.values())
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -71,7 +74,7 @@ const props = defineProps({
|
||||
*/
|
||||
const itemContainerId = computed(() => {
|
||||
let id = 'id_sli_'
|
||||
for (let i in props.entries) {
|
||||
for (let i in entries.value) {
|
||||
id += i + '_'
|
||||
}
|
||||
return id
|
||||
@@ -82,8 +85,8 @@ const itemContainerId = computed(() => {
|
||||
* tests if all entries of the given food are checked
|
||||
*/
|
||||
const isChecked = computed(() => {
|
||||
for (let i in props.entries) {
|
||||
if (!props.entries[i].checked) {
|
||||
for (let i in entries.value) {
|
||||
if (!entries.value[i].checked) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -101,7 +104,7 @@ const isShoppingLineDelayed = computed(() => {
|
||||
* style action button depending on if all items are checked or not
|
||||
*/
|
||||
const actionButtonIcon = computed(() => {
|
||||
if (isChecked.value){
|
||||
if (isChecked.value) {
|
||||
return 'fa-solid fa-plus'
|
||||
}
|
||||
return 'fa-solid fa-check'
|
||||
@@ -116,8 +119,8 @@ const actionButtonIcon = computed(() => {
|
||||
const amounts = computed((): Map<number, ShoppingLineAmount> => {
|
||||
let unitAmounts = new Map<number, ShoppingLineAmount>()
|
||||
|
||||
for (let i in props.entries) {
|
||||
let e = props.entries[i]
|
||||
for (let i in entries.value) {
|
||||
let e = entries.value[i]
|
||||
|
||||
if (!e.checked && !isDelayed(e)
|
||||
|| (e.checked && useUserPreferenceStore().deviceSettings.shopping_show_checked_entries)
|
||||
@@ -147,15 +150,22 @@ const amounts = computed((): Map<number, ShoppingLineAmount> => {
|
||||
return unitAmounts
|
||||
})
|
||||
|
||||
/**
|
||||
* compute the second (info) row of the line item based on the entries and the device settings
|
||||
*/
|
||||
const infoRow = computed(() => {
|
||||
if(props.hideInfoRow){
|
||||
return ''
|
||||
}
|
||||
|
||||
let info_row = []
|
||||
|
||||
let authors = []
|
||||
let recipes = []
|
||||
let meal_pans = []
|
||||
|
||||
for (let i in props.entries) {
|
||||
let e = props.entries[i]
|
||||
for (let i in entries.value) {
|
||||
let e = entries.value[i]
|
||||
|
||||
if (authors.indexOf(e.createdBy.displayName) === -1) {
|
||||
authors.push(e.createdBy.displayName)
|
||||
@@ -203,7 +213,7 @@ function setFoodIgnoredAndChecked(food: Food) {
|
||||
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
|
||||
})
|
||||
|
||||
useShoppingStore().setEntriesCheckedState(props.entries, true, false)
|
||||
useShoppingStore().setEntriesCheckedState(entries.value, true, false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<v-divider></v-divider>
|
||||
|
||||
<template v-for="[i, value] in category.foods" :key="value.food.id">
|
||||
<shopping-line-item :shopping-list-food="value" :entries="Array.from(value.entries.values())"
|
||||
<shopping-line-item :shopping-list-food="value"
|
||||
@clicked="() => {shoppingLineItemDialog = true; shoppingLineItemDialogFood = value;}"></shopping-line-item>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,54 +8,89 @@
|
||||
:is-update="isUpdate()"
|
||||
:model-class="modelClass"
|
||||
:object-name="editingObjName()">
|
||||
|
||||
<v-tabs v-model="tab" :disabled="loading" grow>
|
||||
<v-tab prepend-icon="$mealplan" value="plan">{{ $t('Meal_Plan') }}</v-tab>
|
||||
<v-tab prepend-icon="$shopping" value="shopping">{{ $t('Shopping_list') }}</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-card-text>
|
||||
<v-form :disabled="loading">
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field :label="$t('Title')" v-model="editingObj.title"></v-text-field>
|
||||
<v-date-input
|
||||
v-model="dateRangeValue"
|
||||
:label="$t('Date')"
|
||||
multiple="range"
|
||||
prepend-icon=""
|
||||
prepend-inner-icon="$calendar"
|
||||
></v-date-input>
|
||||
<v-tabs-window v-model="tab">
|
||||
<v-tabs-window-item value="plan">
|
||||
<v-form :disabled="loading">
|
||||
|
||||
<v-input>
|
||||
<v-btn-group elevation="1" class="w-100" divided border>
|
||||
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,-1); updateDate()"><i class="fa-solid fa-minus"></i></v-btn>
|
||||
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, -1); updateDate()"><i class="fa-solid fa-angles-left"></i></v-btn>
|
||||
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, +1); updateDate()"><i class="fa-solid fa-angles-right"></i></v-btn>
|
||||
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,+1); updateDate()"><i class="fa-solid fa-plus"></i></v-btn>
|
||||
</v-btn-group>
|
||||
</v-input>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field :label="$t('Title')" v-model="editingObj.title"></v-text-field>
|
||||
<v-date-input
|
||||
v-model="dateRangeValue"
|
||||
:label="$t('Date')"
|
||||
multiple="range"
|
||||
prepend-icon=""
|
||||
prepend-inner-icon="$calendar"
|
||||
></v-date-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="$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">
|
||||
<ModelSelect model="Recipe" v-model="editingObj.recipe"
|
||||
@update:modelValue="editingObj.servings = editingObj.recipe ? editingObj.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="editingObj.recipe" v-if="editingObj && editingObj.recipe"></recipe-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="12" md="6">
|
||||
<v-textarea :label="$t('Note')" v-model="editingObj.note" rows="3"></v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" v-if="!isUpdate()">
|
||||
<v-checkbox :label="$t('AddToShopping')" v-model="editingObj.addshopping" hide-details></v-checkbox>
|
||||
<!-- <v-checkbox :label="$t('review_shopping')" v-model="addToShopping" hide-details></v-checkbox>-->
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-input>
|
||||
<v-btn-group elevation="1" class="w-100" divided border>
|
||||
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,-1); updateDate()"><i class="fa-solid fa-minus"></i></v-btn>
|
||||
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, -1); updateDate()"><i class="fa-solid fa-angles-left"></i>
|
||||
</v-btn>
|
||||
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, +1); updateDate()"><i class="fa-solid fa-angles-right"></i>
|
||||
</v-btn>
|
||||
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,+1); updateDate()"><i class="fa-solid fa-plus"></i></v-btn>
|
||||
</v-btn-group>
|
||||
</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="$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">
|
||||
<ModelSelect model="Recipe" v-model="editingObj.recipe"
|
||||
@update:modelValue="editingObj.servings = editingObj.recipe ? editingObj.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="editingObj.recipe" v-if="editingObj && editingObj.recipe"></recipe-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="12" md="6">
|
||||
<v-textarea :label="$t('Note')" v-model="editingObj.note" rows="3"></v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" v-if="!isUpdate()">
|
||||
<v-checkbox :label="$t('AddToShopping')" v-model="editingObj.addshopping" hide-details></v-checkbox>
|
||||
<!-- <v-checkbox :label="$t('review_shopping')" v-model="addToShopping" hide-details></v-checkbox>-->
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="shopping">
|
||||
<v-text-field :label="$t('Shopping_input_placeholder')" density="compact" @keyup.enter="addIngredient()" v-model="ingredientInput" hide-details>
|
||||
<template #append>
|
||||
<v-btn
|
||||
density="comfortable"
|
||||
@click="addIngredient()"
|
||||
:icon="ingredientInputIcon"
|
||||
color="create"
|
||||
></v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
|
||||
<v-progress-linear class="mt-2" indeterminate v-if="useShoppingStore().currentlyUpdating"></v-progress-linear>
|
||||
|
||||
<v-list v-if="editingObj.id">
|
||||
<shopping-line-item
|
||||
v-for="slf in useShoppingStore().getMealPlanEntries(editingObj.id)"
|
||||
:shopping-list-food="slf"
|
||||
hide-info-row
|
||||
></shopping-line-item>
|
||||
</v-list>
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
|
||||
|
||||
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</model-editor-base>
|
||||
|
||||
@@ -64,7 +99,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {onMounted, PropType, ref} from "vue";
|
||||
import {ApiApi, MealPlan, MealType} from "@/openapi";
|
||||
import {ApiApi, MealPlan, MealType, ShoppingListEntry} from "@/openapi";
|
||||
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
||||
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
||||
import {DateTime} from "luxon";
|
||||
@@ -74,7 +109,10 @@ import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||
import RecipeCard from "@/components/display/RecipeCard.vue";
|
||||
import {VDateInput} from "vuetify/labs/VDateInput";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {ErrorMessageType, MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import ShoppingLineItem from "@/components/display/ShoppingLineItem.vue";
|
||||
import {IShoppingListFood} from "@/types/Shopping";
|
||||
import {useShoppingStore} from "@/stores/ShoppingStore";
|
||||
|
||||
const props = defineProps({
|
||||
item: {type: {} as PropType<MealPlan>, required: false, default: null},
|
||||
@@ -87,14 +125,18 @@ const emit = defineEmits(['create', 'save', 'delete', 'close'])
|
||||
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, applyItemDefaults, loading, editingObj, modelClass} = useModelEditorFunctions<MealPlan>('MealPlan', emit)
|
||||
|
||||
// object specific data (for selects/display)
|
||||
const tab = ref('shopping')
|
||||
|
||||
const dateRangeValue = ref([] as Date[])
|
||||
|
||||
onMounted(() => {
|
||||
const api = new ApiApi()
|
||||
|
||||
// load meal types and create new object based on default type when initially loading
|
||||
// TODO remove this once moved to user preference from MealType property
|
||||
loading.value = true
|
||||
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) {
|
||||
@@ -107,7 +149,6 @@ onMounted(() => {
|
||||
|
||||
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
|
||||
@@ -119,13 +160,12 @@ onMounted(() => {
|
||||
applyItemDefaults(props.itemDefaults)
|
||||
|
||||
initializeDateRange()
|
||||
console.log(editingObj.value)
|
||||
}, existingItemFunction: () => {
|
||||
initializeDateRange()
|
||||
useShoppingStore().refreshFromAPI(editingObj.value.id!)
|
||||
}
|
||||
},)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user