basic shopping view in MealPlanEditor

This commit is contained in:
vabene1111
2024-12-28 21:56:54 +01:00
parent 4312b5ad65
commit bf4fc9a7aa
7 changed files with 178 additions and 79 deletions

View File

@@ -16,6 +16,7 @@ import PIL.Image
import redis import redis
import requests import requests
from PIL import UnidentifiedImageError from PIL import UnidentifiedImageError
from PIL.ImImagePlugin import number
from PIL.features import check from PIL.features import check
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
@@ -1361,6 +1362,9 @@ class PropertyViewSet(LoggingMixin, viewsets.ModelViewSet):
return self.queryset.filter(space=self.request.space) return self.queryset.filter(space=self.request.space)
@extend_schema_view(list=extend_schema(parameters=[
OpenApiParameter(name='mealplan', description=_('Returns only entries associated with the given mealplan id'), type=int)
]))
class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet): class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
queryset = ShoppingListRecipe.objects queryset = ShoppingListRecipe.objects
serializer_class = ShoppingListRecipeSerializer serializer_class = ShoppingListRecipeSerializer
@@ -1369,6 +1373,14 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
self.queryset = self.queryset.filter(Q(entries__space=self.request.space) | Q(recipe__space=self.request.space)) self.queryset = self.queryset.filter(Q(entries__space=self.request.space) | Q(recipe__space=self.request.space))
# TODO implement test for this
if not self.detail:
mealplan = self.request.query_params.get('mealplan', None)
if mealplan is not None:
self.queryset = self.queryset.filter(mealplan_id=mealplan)
return self.queryset.filter(Q(entries__isnull=True) return self.queryset.filter(Q(entries__isnull=True)
| Q(entries__created_by=self.request.user) | Q(entries__created_by=self.request.user)
| Q(entries__created_by__in=list(self.request.user.get_shopping_share()))).distinct().all() | Q(entries__created_by__in=list(self.request.user.get_shopping_share()))).distinct().all()
@@ -1405,6 +1417,7 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
OpenApiParameter(name='updated_after', OpenApiParameter(name='updated_after',
description=_('Returns only elements updated after the given timestamp in ISO 8601 format.'), description=_('Returns only elements updated after the given timestamp in ISO 8601 format.'),
type=datetime.datetime), type=datetime.datetime),
OpenApiParameter(name='mealplan', description=_('Returns only entries associated with the given mealplan id'), type=int)
])) ]))
class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet): class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
""" """
@@ -1435,6 +1448,7 @@ class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
).distinct().all() ).distinct().all()
updated_after = self.request.query_params.get('updated_after', None) updated_after = self.request.query_params.get('updated_after', None)
mealplan = self.request.query_params.get('mealplan', None)
if not self.detail: if not self.detail:
# to keep the endpoint small, only return entries as old as user preference recent days # to keep the endpoint small, only return entries as old as user preference recent days
@@ -1442,10 +1456,12 @@ class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
week_ago = today_start - datetime.timedelta(days=min(self.request.user.userpreference.shopping_recent_days, 14)) week_ago = today_start - datetime.timedelta(days=min(self.request.user.userpreference.shopping_recent_days, 14))
self.queryset = self.queryset.filter((Q(checked=False) | Q(completed_at__gte=week_ago))) self.queryset = self.queryset.filter((Q(checked=False) | Q(completed_at__gte=week_ago)))
if mealplan is not None:
self.queryset = self.queryset.filter(list_recipe__mealplan_id=mealplan)
try: try:
if updated_after: if updated_after:
updated_after = parse_datetime(updated_after) updated_after = parse_datetime(updated_after)
print('adding filter updated_after', updated_after)
self.queryset = self.queryset.filter(updated_at__gte=updated_after) self.queryset = self.queryset.filter(updated_at__gte=updated_after)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
@@ -1588,9 +1604,8 @@ class AutomationViewSet(LoggingMixin, StandardFilterModelViewSet):
return self.queryset.filter(space=self.request.space).all() return self.queryset.filter(space=self.request.space).all()
# TODO explain what internal_note is for
@extend_schema_view(list=extend_schema(parameters=[ @extend_schema_view(list=extend_schema(parameters=[
OpenApiParameter(name='internal_note', description=_('I have no idea what internal_note is for.'), type=str) OpenApiParameter(name='internal_note', description=_('Text field to store data that gets carried over to the UserSpace created from the InviteLink'), type=str)
])) ]))
class InviteLinkViewSet(LoggingMixin, StandardFilterModelViewSet): class InviteLinkViewSet(LoggingMixin, StandardFilterModelViewSet):
queryset = InviteLink.objects queryset = InviteLink.objects
@@ -1806,7 +1821,7 @@ class ImageToRecipeView(APIView):
""" """
serializer = ImportImageSerializer(data=request.data, partial=True) serializer = ImportImageSerializer(data=request.data, partial=True)
if serializer.is_valid(): if serializer.is_valid():
#generativeai.configure(api_key=GOOGLE_AI_API_KEY) # generativeai.configure(api_key=GOOGLE_AI_API_KEY)
# model = generativeai.GenerativeModel('gemini-1.5-flash-latest') # model = generativeai.GenerativeModel('gemini-1.5-flash-latest')
# img = PIL.Image.open('') # img = PIL.Image.open('')

View File

@@ -7,14 +7,13 @@
<!-- </div>--> <!-- </div>-->
<div class="flex-grow-1 p-2"> <div class="flex-grow-1 p-2">
<div class="d-flex"> <div class="d-flex">
<div class="d-flex flex-column pr-2"> <div class="d-flex flex-column pr-2">
<span v-for="[i, a] in amounts" v-bind:key="a.key"> <span v-for="[i, a] in amounts" v-bind:key="a.key">
<span> <span>
<i class="fas fa-check text-success fa-fw" v-if="a.checked"></i> <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}"> <span :class="{'text-disabled': a.checked || a.delayed}">
{{ $n(a.amount) }} {{ $n(a.amount) }}
<span v-if="a.unit">{{ a.unit.name }}</span> <span v-if="a.unit">{{ a.unit.name }}</span>
@@ -25,7 +24,7 @@
<br/> <br/>
</span> </span>
</div> </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/> {{ shoppingListFood.food.name }} <br/>
<span v-if="infoRow"><small class="text-disabled">{{ infoRow }}</small></span> <span v-if="infoRow"><small class="text-disabled">{{ infoRow }}</small></span>
</div> </div>
@@ -62,8 +61,12 @@ import {isDelayed, isShoppingListFoodDelayed} from "@/utils/logic_utils";
const emit = defineEmits(['clicked']) const emit = defineEmits(['clicked'])
const props = defineProps({ const props = defineProps({
entries: {type: Array as PropType<Array<ShoppingListEntry>>, required: true},
shoppingListFood: {type: {} as PropType<IShoppingListFood>, 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(() => { const itemContainerId = computed(() => {
let id = 'id_sli_' let id = 'id_sli_'
for (let i in props.entries) { for (let i in entries.value) {
id += i + '_' id += i + '_'
} }
return id return id
@@ -82,8 +85,8 @@ const itemContainerId = computed(() => {
* tests if all entries of the given food are checked * tests if all entries of the given food are checked
*/ */
const isChecked = computed(() => { const isChecked = computed(() => {
for (let i in props.entries) { for (let i in entries.value) {
if (!props.entries[i].checked) { if (!entries.value[i].checked) {
return false return false
} }
} }
@@ -101,7 +104,7 @@ const isShoppingLineDelayed = computed(() => {
* style action button depending on if all items are checked or not * style action button depending on if all items are checked or not
*/ */
const actionButtonIcon = computed(() => { const actionButtonIcon = computed(() => {
if (isChecked.value){ if (isChecked.value) {
return 'fa-solid fa-plus' return 'fa-solid fa-plus'
} }
return 'fa-solid fa-check' return 'fa-solid fa-check'
@@ -116,8 +119,8 @@ const actionButtonIcon = computed(() => {
const amounts = computed((): Map<number, ShoppingLineAmount> => { const amounts = computed((): Map<number, ShoppingLineAmount> => {
let unitAmounts = new Map<number, ShoppingLineAmount>() let unitAmounts = new Map<number, ShoppingLineAmount>()
for (let i in props.entries) { for (let i in entries.value) {
let e = props.entries[i] let e = entries.value[i]
if (!e.checked && !isDelayed(e) if (!e.checked && !isDelayed(e)
|| (e.checked && useUserPreferenceStore().deviceSettings.shopping_show_checked_entries) || (e.checked && useUserPreferenceStore().deviceSettings.shopping_show_checked_entries)
@@ -147,15 +150,22 @@ const amounts = computed((): Map<number, ShoppingLineAmount> => {
return unitAmounts return unitAmounts
}) })
/**
* compute the second (info) row of the line item based on the entries and the device settings
*/
const infoRow = computed(() => { const infoRow = computed(() => {
if(props.hideInfoRow){
return ''
}
let info_row = [] let info_row = []
let authors = [] let authors = []
let recipes = [] let recipes = []
let meal_pans = [] let meal_pans = []
for (let i in props.entries) { for (let i in entries.value) {
let e = props.entries[i] let e = entries.value[i]
if (authors.indexOf(e.createdBy.displayName) === -1) { if (authors.indexOf(e.createdBy.displayName) === -1) {
authors.push(e.createdBy.displayName) authors.push(e.createdBy.displayName)
@@ -203,7 +213,7 @@ function setFoodIgnoredAndChecked(food: Food) {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err) useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}) })
useShoppingStore().setEntriesCheckedState(props.entries, true, false) useShoppingStore().setEntriesCheckedState(entries.value, true, false)
} }
/** /**

View File

@@ -109,7 +109,7 @@
<v-divider></v-divider> <v-divider></v-divider>
<template v-for="[i, value] in category.foods" :key="value.food.id"> <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> @clicked="() => {shoppingLineItemDialog = true; shoppingLineItemDialogFood = value;}"></shopping-line-item>
</template> </template>

View File

@@ -8,54 +8,89 @@
:is-update="isUpdate()" :is-update="isUpdate()"
:model-class="modelClass" :model-class="modelClass"
:object-name="editingObjName()"> :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-card-text>
<v-form :disabled="loading">
<v-row> <v-tabs-window v-model="tab">
<v-col cols="12" md="6"> <v-tabs-window-item value="plan">
<v-text-field :label="$t('Title')" v-model="editingObj.title"></v-text-field> <v-form :disabled="loading">
<v-date-input
v-model="dateRangeValue"
:label="$t('Date')"
multiple="range"
prepend-icon=""
prepend-inner-icon="$calendar"
></v-date-input>
<v-input> <v-row>
<v-btn-group elevation="1" class="w-100" divided border> <v-col cols="12" md="6">
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,-1); updateDate()"><i class="fa-solid fa-minus"></i></v-btn> <v-text-field :label="$t('Title')" v-model="editingObj.title"></v-text-field>
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, -1); updateDate()"><i class="fa-solid fa-angles-left"></i></v-btn> <v-date-input
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, +1); updateDate()"><i class="fa-solid fa-angles-right"></i></v-btn> v-model="dateRangeValue"
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,+1); updateDate()"><i class="fa-solid fa-plus"></i></v-btn> :label="$t('Date')"
</v-btn-group> multiple="range"
</v-input> prepend-icon=""
prepend-inner-icon="$calendar"
></v-date-input>
<ModelSelect model="MealType" :allow-create="true" v-model="editingObj.mealType"></ModelSelect> <v-input>
<v-number-input control-variant="split" :min="0" v-model="editingObj.servings" :label="$t('Servings')"></v-number-input> <v-btn-group elevation="1" class="w-100" divided border>
<ModelSelect model="User" :allow-create="false" v-model="editingObj.shared" item-label="displayName" mode="tags"></ModelSelect> <v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,-1); updateDate()"><i class="fa-solid fa-minus"></i></v-btn>
</v-col> <v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, -1); updateDate()"><i class="fa-solid fa-angles-left"></i>
<v-col cols="12" md="6"> </v-btn>
<ModelSelect model="Recipe" v-model="editingObj.recipe" <v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, +1); updateDate()"><i class="fa-solid fa-angles-right"></i>
@update:modelValue="editingObj.servings = editingObj.recipe ? editingObj.recipe.servings : 1"></ModelSelect> </v-btn>
<!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>--> <v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,+1); updateDate()"><i class="fa-solid fa-plus"></i></v-btn>
<!--TODO create days input with +/- synced to date --> </v-btn-group>
<recipe-card :recipe="editingObj.recipe" v-if="editingObj && editingObj.recipe"></recipe-card> </v-input>
</v-col>
</v-row> <ModelSelect model="MealType" :allow-create="true" v-model="editingObj.mealType"></ModelSelect>
<v-row dense> <v-number-input control-variant="split" :min="0" v-model="editingObj.servings" :label="$t('Servings')"></v-number-input>
<v-col cols="12" md="6"> <ModelSelect model="User" :allow-create="false" v-model="editingObj.shared" item-label="displayName" mode="tags"></ModelSelect>
<v-textarea :label="$t('Note')" v-model="editingObj.note" rows="3"></v-textarea> </v-col>
</v-col> <v-col cols="12" md="6">
<v-col cols="12" md="6" v-if="!isUpdate()"> <ModelSelect model="Recipe" v-model="editingObj.recipe"
<v-checkbox :label="$t('AddToShopping')" v-model="editingObj.addshopping" hide-details></v-checkbox> @update:modelValue="editingObj.servings = editingObj.recipe ? editingObj.recipe.servings : 1"></ModelSelect>
<!-- <v-checkbox :label="$t('review_shopping')" v-model="addToShopping" hide-details></v-checkbox>--> <!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>-->
</v-col> <!--TODO create days input with +/- synced to date -->
</v-row> <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> </v-card-text>
</model-editor-base> </model-editor-base>
@@ -64,7 +99,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, PropType, ref} from "vue"; 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 ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions"; import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
import {DateTime} from "luxon"; import {DateTime} from "luxon";
@@ -74,7 +109,10 @@ import ModelSelect from "@/components/inputs/ModelSelect.vue";
import RecipeCard from "@/components/display/RecipeCard.vue"; import RecipeCard from "@/components/display/RecipeCard.vue";
import {VDateInput} from "vuetify/labs/VDateInput"; import {VDateInput} from "vuetify/labs/VDateInput";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; 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({ const props = defineProps({
item: {type: {} as PropType<MealPlan>, required: false, default: null}, 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) const {setupState, deleteObject, saveObject, isUpdate, editingObjName, applyItemDefaults, loading, editingObj, modelClass} = useModelEditorFunctions<MealPlan>('MealPlan', emit)
// object specific data (for selects/display) // object specific data (for selects/display)
const tab = ref('shopping')
const dateRangeValue = ref([] as Date[]) const dateRangeValue = ref([] as Date[])
onMounted(() => { onMounted(() => {
const api = new ApiApi() 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 => { api.apiMealTypeList().then(r => {
// TODO remove this once moved to user preference from MealType property
let defaultMealType = {} as MealType let defaultMealType = {} as MealType
r.results.forEach(r => { r.results.forEach(r => {
if (r._default) { if (r._default) {
@@ -107,7 +149,6 @@ onMounted(() => {
setupState(props.item, props.itemId, { setupState(props.item, props.itemId, {
newItemFunction: () => { newItemFunction: () => {
console.log('running new Item Function')
editingObj.value.fromDate = DateTime.now().toJSDate() editingObj.value.fromDate = DateTime.now().toJSDate()
editingObj.value.toDate = DateTime.now().toJSDate() editingObj.value.toDate = DateTime.now().toJSDate()
editingObj.value.shared = useUserPreferenceStore().userSettings.planShare editingObj.value.shared = useUserPreferenceStore().userSettings.planShare
@@ -119,13 +160,12 @@ onMounted(() => {
applyItemDefaults(props.itemDefaults) applyItemDefaults(props.itemDefaults)
initializeDateRange() initializeDateRange()
console.log(editingObj.value)
}, existingItemFunction: () => { }, existingItemFunction: () => {
initializeDateRange() initializeDateRange()
useShoppingStore().refreshFromAPI(editingObj.value.id!)
} }
},) },)
}) })
}) })
/** /**

View File

@@ -1252,6 +1252,7 @@ export interface ApiShoppingListEntryDestroyRequest {
} }
export interface ApiShoppingListEntryListRequest { export interface ApiShoppingListEntryListRequest {
mealplan?: number;
page?: number; page?: number;
pageSize?: number; pageSize?: number;
updatedAfter?: Date; updatedAfter?: Date;
@@ -1285,6 +1286,7 @@ export interface ApiShoppingListRecipeDestroyRequest {
} }
export interface ApiShoppingListRecipeListRequest { export interface ApiShoppingListRecipeListRequest {
mealplan?: number;
page?: number; page?: number;
pageSize?: number; pageSize?: number;
} }
@@ -4023,7 +4025,7 @@ export class ApiApi extends runtime.BaseAPI {
/** /**
*/ */
async apiGetRecipeFileRetrieveRaw(requestParameters: ApiGetRecipeFileRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction) { async apiGetRecipeFileRetrieveRaw(requestParameters: ApiGetRecipeFileRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters['recipeId'] == null) { if (requestParameters['recipeId'] == null) {
throw new runtime.RequiredError( throw new runtime.RequiredError(
'recipeId', 'recipeId',
@@ -4046,13 +4048,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters, query: queryParameters,
}, initOverrides); }, initOverrides);
return response; return new runtime.VoidApiResponse(response);
} }
/** /**
*/ */
async apiGetRecipeFileRetrieve(requestParameters: ApiGetRecipeFileRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction){ async apiGetRecipeFileRetrieve(requestParameters: ApiGetRecipeFileRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
return await this.apiGetRecipeFileRetrieveRaw(requestParameters, initOverrides); await this.apiGetRecipeFileRetrieveRaw(requestParameters, initOverrides);
} }
/** /**
@@ -9243,6 +9245,10 @@ export class ApiApi extends runtime.BaseAPI {
async apiShoppingListEntryListRaw(requestParameters: ApiShoppingListEntryListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedShoppingListEntryList>> { async apiShoppingListEntryListRaw(requestParameters: ApiShoppingListEntryListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedShoppingListEntryList>> {
const queryParameters: any = {}; const queryParameters: any = {};
if (requestParameters['mealplan'] != null) {
queryParameters['mealplan'] = requestParameters['mealplan'];
}
if (requestParameters['page'] != null) { if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page']; queryParameters['page'] = requestParameters['page'];
} }
@@ -9532,6 +9538,10 @@ export class ApiApi extends runtime.BaseAPI {
async apiShoppingListRecipeListRaw(requestParameters: ApiShoppingListRecipeListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedShoppingListRecipeList>> { async apiShoppingListRecipeListRaw(requestParameters: ApiShoppingListRecipeListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedShoppingListRecipeList>> {
const queryParameters: any = {}; const queryParameters: any = {};
if (requestParameters['mealplan'] != null) {
queryParameters['mealplan'] = requestParameters['mealplan'];
}
if (requestParameters['page'] != null) { if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page']; queryParameters['page'] = requestParameters['page'];
} }

View File

@@ -130,7 +130,7 @@ export interface MealPlan {
* @type {boolean} * @type {boolean}
* @memberof MealPlan * @memberof MealPlan
*/ */
addshopping: boolean; addshopping?: boolean;
} }
/** /**
@@ -145,7 +145,6 @@ export function instanceOfMealPlan(value: object): value is MealPlan {
if (!('recipeName' in value) || value['recipeName'] === undefined) return false; if (!('recipeName' in value) || value['recipeName'] === undefined) return false;
if (!('mealTypeName' in value) || value['mealTypeName'] === undefined) return false; if (!('mealTypeName' in value) || value['mealTypeName'] === undefined) return false;
if (!('shopping' in value) || value['shopping'] === undefined) return false; if (!('shopping' in value) || value['shopping'] === undefined) return false;
if (!('addshopping' in value) || value['addshopping'] === undefined) return false;
return true; return true;
} }
@@ -173,7 +172,7 @@ export function MealPlanFromJSONTyped(json: any, ignoreDiscriminator: boolean):
'recipeName': json['recipe_name'], 'recipeName': json['recipe_name'],
'mealTypeName': json['meal_type_name'], 'mealTypeName': json['meal_type_name'],
'shopping': json['shopping'], 'shopping': json['shopping'],
'addshopping': json['addshopping'], 'addshopping': json['addshopping'] == null ? undefined : json['addshopping'],
}; };
} }

View File

@@ -1,5 +1,5 @@
import {acceptHMRUpdate, defineStore} from "pinia" import {acceptHMRUpdate, defineStore} from "pinia"
import {ApiApi, Food, Recipe, ShoppingListEntry, ShoppingListEntryBulk, ShoppingListRecipe, Supermarket, SupermarketCategory} from "@/openapi"; import {ApiApi, ApiShoppingListEntryListRequest, Food, Recipe, ShoppingListEntry, ShoppingListEntryBulk, ShoppingListRecipe, Supermarket, SupermarketCategory} from "@/openapi";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import { import {
IShoppingExportEntry, IShoppingExportEntry,
@@ -149,6 +149,25 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
return items return items
} }
/**
* very simple list of shopping list entries as IShoppingListFood array filtered by a certain mealplan
* @param mealPlanId ID of mealplan
*/
function getMealPlanEntries(mealPlanId: number) {
let items: IShoppingListFood[] = []
entries.value.forEach(shoppingListEntry => {
if (shoppingListEntry.recipeMealplan && shoppingListEntry.recipeMealplan.mealplan == mealPlanId) {
items.push({
food: shoppingListEntry.food,
entries: new Map<number, ShoppingListEntry>().set(shoppingListEntry.id!, shoppingListEntry)
} as IShoppingListFood)
}
})
return items
}
/** /**
* checks if failed items are contained in the sync queue * checks if failed items are contained in the sync queue
*/ */
@@ -163,16 +182,22 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
/** /**
* Retrieves all shopping related data (shopping list entries, supermarkets, supermarket categories and shopping list recipes) from API * Retrieves all shopping related data (shopping list entries, supermarkets, supermarket categories and shopping list recipes) from API
* @param mealPlanId optionally filter by mealplan ID and only load entries associated with that
*/ */
function refreshFromAPI() { function refreshFromAPI(mealPlanId?: number) {
if (!currentlyUpdating.value) { if (!currentlyUpdating.value) {
currentlyUpdating.value = true currentlyUpdating.value = true
autoSyncLastTimestamp.value = new Date(); autoSyncLastTimestamp.value = new Date();
let api = new ApiApi() let api = new ApiApi()
api.apiShoppingListEntryList().then((r) => { let requestParameters = {pageSize: 200} as ApiShoppingListEntryListRequest
if (mealPlanId) {
requestParameters.mealplan = mealPlanId
}
api.apiShoppingListEntryList(requestParameters).then((r) => {
entries.value = new Map<number, ShoppingListEntry> entries.value = new Map<number, ShoppingListEntry>
// TODO load all pages // TODO properly load pages
r.results.forEach((e) => { r.results.forEach((e) => {
entries.value.set(e.id!, e) entries.value.set(e.id!, e)
}) })
@@ -183,7 +208,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err) useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}) })
api.apiSupermarketList().then(r => { api.apiSupermarketCategoryList().then(r => {
supermarketCategories.value = r.results supermarketCategories.value = r.results
}).catch((err) => { }).catch((err) => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err) useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
@@ -552,7 +577,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
setFoodIgnoredState, setFoodIgnoredState,
delayEntries: setEntriesDelayedState, delayEntries: setEntriesDelayedState,
getAssociatedRecipes, getAssociatedRecipes,
getMealPlanEntries,
} }
}) })