This commit is contained in:
vabene1111
2025-12-03 17:52:49 +01:00
parent 9dfc9e1020
commit e1a9938c0b
28 changed files with 306 additions and 178 deletions

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.8 on 2025-12-03 16:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0233_food_shopping_lists_shoppinglistentry_shopping_lists_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='shoppinglist',
options={'ordering': ('pk',)},
),
migrations.AddField(
model_name='userpreference',
name='shopping_update_food_lists',
field=models.BooleanField(default=True),
),
]

View File

@@ -551,6 +551,7 @@ class UserPreference(models.Model, PermissionModelMixin):
show_step_ingredients = models.BooleanField(default=True)
default_delay = models.DecimalField(default=4, max_digits=8, decimal_places=4)
shopping_recent_days = models.PositiveIntegerField(default=7)
shopping_update_food_lists = models.BooleanField(default=True)
csv_delim = models.CharField(max_length=2, default=",")
csv_prefix = models.CharField(max_length=10, blank=True, )

View File

@@ -502,6 +502,20 @@ class SpacedModelSerializer(serializers.ModelSerializer):
return super().create(validated_data)
class ShoppingListSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = ShoppingList.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data)
return obj
class Meta:
model = ShoppingList
fields = ('id', 'name', 'description', 'color',) # returning dates breaks breaks shopping list deviceSetting save due to date retrieved from local storage as string
read_only_fields = ('id',)
class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
@@ -551,7 +565,7 @@ class UserPreferenceSerializer(WritableNestedModelSerializer):
'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping',
'food_inherit_default', 'default_delay',
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days',
'csv_delim', 'csv_prefix',
'csv_delim', 'csv_prefix', 'shopping_update_food_lists',
'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'show_step_ingredients',
'food_children_exist'
)
@@ -760,6 +774,7 @@ class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataModelMixin):
category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True)
shopping_lists = ShoppingListSerializer(many=True, required=False)
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
@@ -770,7 +785,7 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataMo
class Meta:
model = Supermarket
fields = ('id', 'name', 'description', 'category_to_supermarket', 'open_data_slug')
fields = ('id', 'name', 'description', 'shopping_lists', 'category_to_supermarket', 'open_data_slug')
class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin):
@@ -854,7 +869,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
substitute_onhand = serializers.SerializerMethodField('get_substitute_onhand')
substitute = FoodSimpleSerializer(many=True, allow_null=True, required=False)
parent = IntegerField(read_only=True)
shopping_lists = ShoppingListSerializer(many=True, required=False)
properties = PropertySerializer(many=True, allow_null=True, required=False)
properties_food_unit = UnitSerializer(allow_null=True, required=False)
properties_food_amount = CustomDecimalField(required=False)
@@ -965,7 +980,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
fields = (
'id', 'name', 'plural_name', 'description', 'shopping', 'recipe', 'url', 'properties', 'properties_food_amount', 'properties_food_unit', 'fdc_id',
'food_onhand', 'supermarket_category', 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping',
'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug',
'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug', 'shopping_lists',
)
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
@@ -1415,20 +1430,6 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
read_only_fields = ('id', 'created_by',)
class ShoppingListSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip()
space = validated_data.pop('space', self.context['request'].space)
obj, created = ShoppingList.objects.get_or_create(name__iexact=validated_data['name'], space=space, defaults=validated_data)
return obj
class Meta:
model = ShoppingList
fields = ('id', 'name', 'description', 'color', 'created_at', 'updated_at',)
read_only_fields = ('id', 'created_at', 'updated_at',)
class ShoppingListEntrySerializer(WritableNestedModelSerializer):
food = FoodSimpleSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True, required=False)
@@ -1481,7 +1482,13 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
created_by=self.context['request'].user)
del validated_data['mealplan_id']
return super().create(validated_data)
obj = super().create(validated_data)
if self.context['request'].user.userpreference.shopping_update_food_lists:
obj.shopping_lists.clear()
obj.shopping_lists.set(obj.food.shopping_lists.all())
return obj
def update(self, instance, validated_data):
user = self.context['request'].user

View File

@@ -1,6 +1,6 @@
<template>
<v-dialog v-model="dialog" activator="parent" style="max-width: 75vw;">
<v-dialog v-model="dialog" :activator="props.activator" style="max-width: 75vw;">
<v-card>
<v-closable-card-title :title="$t('Export')" v-model="dialog"></v-closable-card-title>
@@ -48,7 +48,7 @@
<script setup lang="ts">
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
import {computed, ref} from "vue";
import {computed, PropType, ref, shallowRef} from "vue";
import {useShoppingStore} from "@/stores/ShoppingStore.ts";
import {isEntryVisible, isShoppingCategoryVisible, isShoppingListFoodVisible} from "@/utils/logic_utils.ts";
import {useI18n} from "vue-i18n";
@@ -56,10 +56,15 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
import {ShoppingListEntry} from "@/openapi";
import BtnCopy from "@/components/buttons/BtnCopy.vue";
import {useClipboard} from "@vueuse/core";
import {EditorSupportedModels, getGenericModelFromString} from "@/types/Models.ts";
const {t} = useI18n()
const {copy} = useClipboard()
const props = defineProps({
activator: {default: 'parent'},
})
const dialog = defineModel<boolean>()
const mode = ref<'md_list' | 'md_table' | 'csv'>('md_list')
@@ -78,7 +83,7 @@ const exportText = computed(() => {
textArray.push(formatHeader())
useShoppingStore().getEntriesByGroup.forEach(category => {
useShoppingStore().entriesByGroup.forEach(category => {
if (isShoppingCategoryVisible(category)) {
if (category.name === useShoppingStore().UNDEFINED_CATEGORY) {
textArray.push(formatCategory(t('NoCategory')))

View File

@@ -140,6 +140,7 @@ import {useShoppingStore} from "@/stores/ShoppingStore";
import {isDelayed, isShoppingListFoodDelayed} from "@/utils/logic_utils";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
import ShoppingListsBar from "@/components/display/ShoppingListsBar.vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
const {mobile} = useDisplay()
@@ -197,6 +198,15 @@ function shoppingListUpdate(shoppingLists: ShoppingList[]) {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}))
})
if (useUserPreferenceStore().userSettings.shoppingUpdateFoodLists){
shoppingListFood.value.food.shoppingLists = shoppingLists
promises.push(api.apiFoodUpdate({id: shoppingListFood.value.food.id!, food: shoppingListFood.value.food}).then(r => {
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}))
}
Promise.all(promises).finally(() => shoppingListUpdateLoading.value = false)
}

View File

@@ -25,9 +25,13 @@
<v-list density="compact">
<v-list-item @click="useShoppingStore().undoChange()" prepend-icon="fa-solid fa-arrow-rotate-left">{{ $t('Undo') }}</v-list-item>
<v-list-item @click="exportDialog = true" link prepend-icon="fa-solid fa-download">
{{ $t('Export') }}
</v-list-item>
<v-divider></v-divider>
<v-list-item>
<v-select hide-details :items="groupingOptionsItems" v-model="useUserPreferenceStore().deviceSettings.shopping_selected_grouping" :label="$t('GroupBy')">
<v-select hide-details :items="groupingOptionsItems" v-model="useUserPreferenceStore().deviceSettings.shopping_selected_grouping"
@update:modelValue="useShoppingStore().updateEntriesStructure()" :label="$t('GroupBy')">
</v-select>
</v-list-item>
<v-list-item v-if="useUserPreferenceStore().deviceSettings.shopping_selected_grouping == ShoppingGroupingOptions.CATEGORY">
@@ -65,65 +69,75 @@
</v-list>
</v-menu>
<v-btn height="100%" rounded="0" variant="plain">
<i class="fa-solid fa-download"></i>
<shopping-export-dialog></shopping-export-dialog>
</v-btn>
<!-- <v-btn height="100%" rounded="0" variant="plain">-->
<!-- <i class="fa-solid fa-download"></i>-->
<!-- <shopping-export-dialog></shopping-export-dialog>-->
<!-- </v-btn>-->
<v-btn height="100%" rounded="0" variant="plain" @click="useShoppingStore().undoChange()">
<i class="fa-solid fa-arrow-rotate-left"></i>
</v-btn>
<!-- <v-btn height="100%" rounded="0" variant="plain" @click="useShoppingStore().undoChange()">-->
<!-- <i class="fa-solid fa-arrow-rotate-left"></i>-->
<!-- </v-btn>-->
</v-tabs>
<shopping-export-dialog v-model="exportDialog" activator="model"></shopping-export-dialog>
<v-window v-model="currentTab">
<v-window-item value="shopping">
<v-container>
<!-- <v-row class="pa-0" dense>-->
<!-- <v-col class="pa-0">-->
<!-- <v-chip-group v-model="useUserPreferenceStore().deviceSettings.shopping_selected_supermarket" v-if="supermarkets.length > 0">-->
<!-- <v-chip v-for="s in supermarkets" :value="s" :key="s.id" label density="compact" variant="outlined" color="primary">{{ s.name }}</v-chip>-->
<!-- </v-chip-group>-->
<!-- </v-col>-->
<!-- </v-row>-->
<!-- <v-row class="pa-0" dense>-->
<!-- <v-col class="pa-0">-->
<!-- <v-chip-group v-model="useUserPreferenceStore().deviceSettings.shopping_selected_supermarket" v-if="supermarkets.length > 0">-->
<!-- <v-chip v-for="s in supermarkets" :value="s" :key="s.id" label density="compact" variant="outlined" color="primary">{{ s.name }}</v-chip>-->
<!-- </v-chip-group>-->
<!-- </v-col>-->
<!-- </v-row>-->
<v-row class="pa-0" dense>
<v-col class="pa-0" cols="6">
<v-chip label density="compact" variant="outlined" :prepend-icon="TSupermarket.icon" append-icon="fa-solid fa-caret-down">
<template v-if="useUserPreferenceStore().deviceSettings.shopping_selected_supermarket != null">
{{useUserPreferenceStore().deviceSettings.shopping_selected_supermarket.name}}
</template>
<template v-else>{{ $t('Supermarket') }}</template>
<v-col class="pa-0">
<v-chip label density="compact" variant="outlined" style="max-width: 50%;" :prepend-icon="TSupermarket.icon" append-icon="fa-solid fa-caret-down">
<span v-if="useUserPreferenceStore().deviceSettings.shopping_selected_supermarket != null">
{{ useUserPreferenceStore().deviceSettings.shopping_selected_supermarket.name }}
</span>
<span v-else>{{ $t('Supermarket') }}</span>
<v-menu activator="parent">
<v-list density="compact">
<v-list-item @click="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list = []; useShoppingStore().updateEntriesStructure()">
{{ $t('SelectNone') }}
</v-list-item>
<v-list-item v-for="s in supermarkets" :key="s.id" @click="useUserPreferenceStore().deviceSettings.shopping_selected_supermarket = s">
{{ s.name }}
</v-list-item>
<v-list-item prepend-icon="$create" :to="{name: 'ModelEditPage', params: {model: 'Supermarket'}}">
{{$t('Create')}}
{{ $t('Create') }}
</v-list-item>
</v-list>
</v-menu>
</v-chip>
</v-col>
<v-col class="pa-0" cols="6">
<v-chip label density="compact" variant="outlined" :prepend-icon="TShoppingList.icon" append-icon="fa-solid fa-caret-down">
<template v-if="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list != null">
{{useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list.name}}
<v-chip label density="compact" variant="outlined" style="max-width: 50%;" :prepend-icon="TShoppingList.icon" append-icon="fa-solid fa-caret-down">
<template v-if="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list.length > 0">
{{ shoppingLists.filter(sl => useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list.includes(sl.id)).flatMap(sl => sl.name).join(', ') }}
</template>
<template v-else>{{ $t('ShoppingList') }}</template>
<v-menu activator="parent">
<v-list density="compact">
<v-list-item @click="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list = null">
{{$t('All')}}
<v-menu activator="parent" :close-on-content-click="false">
<v-list density="compact" v-model:selected="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list"
@update:selected="useShoppingStore().updateEntriesStructure()" select-strategy="leaf">
<v-list-item @click="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list = []; useShoppingStore().updateEntriesStructure()">
{{ $t('All') }}
</v-list-item>
<v-list-item v-for="s in shoppingLists" :key="s.id" @click="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list = s">
<v-list-item v-for="s in shoppingLists" :key="s.id" :value="s.id">
<template v-slot:prepend="{ isSelected, select }">
<v-list-item-action start>
<v-checkbox-btn :model-value="isSelected" @update:model-value="select"></v-checkbox-btn>
</v-list-item-action>
</template>
{{ s.name }}
</v-list-item>
<v-list-item prepend-icon="$create" :to="{name: 'ModelEditPage', params: {model: 'ShoppingList'}}">
{{$t('Create')}}
{{ $t('Create') }}
</v-list-item>
</v-list>
</v-menu>
@@ -152,16 +166,16 @@
<v-skeleton-loader type="list-item"></v-skeleton-loader>
</v-list>
<v-list class="mt-3" density="compact" v-else>
<template v-for="category in useShoppingStore().getEntriesByGroup" :key="category.name">
<template v-for="category in useShoppingStore().entriesByGroup" :key="category.name">
<v-list-subheader v-if="category.name === useShoppingStore().UNDEFINED_CATEGORY"><i>{{ $t('NoCategory') }}</i></v-list-subheader>
<v-list-subheader v-else>{{ category.name }}</v-list-subheader>
<v-divider></v-divider>
<v-list-subheader v-if="category.name === useShoppingStore().UNDEFINED_CATEGORY"><i>{{ $t('NoCategory') }}</i></v-list-subheader>
<v-list-subheader v-else>{{ category.name }}</v-list-subheader>
<v-divider></v-divider>
<template v-for="[i, value] in category.foods" :key="value.food.id">
<shopping-line-item :shopping-list-food="value"></shopping-line-item>
</template>
<template v-for="[i, value] in category.foods" :key="value.food.id">
<shopping-line-item :shopping-list-food="value"></shopping-line-item>
</template>
</template>
</v-list>
@@ -289,7 +303,7 @@
<script setup lang="ts">
import {computed, onMounted, ref} from "vue";
import {computed, onMounted, ref, toRef} from "vue";
import {useShoppingStore} from "@/stores/ShoppingStore";
import {ApiApi, Recipe, ResponseError, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Supermarket} from "@/openapi";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
@@ -312,6 +326,7 @@ import {TShoppingList, TSupermarket} from "@/types/Models.ts";
const {t} = useI18n()
const exportDialog = ref(false)
const currentTab = ref("shopping")
const supermarkets = ref([] as Supermarket[])
const shoppingLists = ref([] as ShoppingList[])

View File

@@ -30,6 +30,7 @@
<v-textarea :label="$t('Description')" v-model="editingObj.description"></v-textarea>
<!-- TODO fix card overflow invisible, overflow-visible class is not working -->
<model-select :label="$t('Category')" v-model="editingObj.supermarketCategory" model="SupermarketCategory" allow-create append-to-body></model-select>
<model-select :label="$t('ShoppingList')" :hint="$t('DefaultShoppingListHelp')" v-model="editingObj.shoppingLists" model="ShoppingList" mode="tags" allow-create append-to-body></model-select>
</v-form>
</v-tabs-window-item>

View File

@@ -27,6 +27,7 @@
<v-checkbox :label="$t('mealplan_autoinclude_related')" :hint="$t('mealplan_autoinclude_related_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.mealplanAutoincludeRelated"></v-checkbox>
<v-checkbox :label="$t('shopping_add_onhand')" :hint="$t('shopping_add_onhand_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.shoppingAddOnhand"></v-checkbox>
<v-checkbox :label="$t('filter_to_supermarket')" :hint="$t('filter_to_supermarket_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.filterToSupermarket"></v-checkbox>
<v-checkbox :label="$t('UpdateFoodLists')" :hint="$t('UpdateFoodListsHelp')" persistent-hint v-model="useUserPreferenceStore().userSettings.shoppingUpdateFoodLists"></v-checkbox>
<v-number-input
class="mt-2"

View File

@@ -152,6 +152,7 @@
"Decimals": "Nachkommastellen",
"Default": "Standard",
"DefaultPage": "Standardseite",
"DefaultShoppingListHelp": "Standard Liste wenn dieses Lebensmittel auf die Einkaufsliste gesetzt wird.",
"Default_Unit": "Standardeinheit",
"DelayFor": "Um {hours} Stunden verschieben",
"DelayUntil": "Verzögerung bis",
@@ -514,7 +515,7 @@
"Shopping_Categories": "Einkaufskategorien",
"Shopping_Category": "Einkaufskategorie",
"Shopping_List_Empty": "Deine Einkaufsliste ist aktuell leer. Einträge können über das Kontextmenü hinzugefügt werden (Rechtsklick auf einen Eintrag oder Klick auf das Menü-Icon)",
"Shopping_input_placeholder": "z.B. Kartoffeln/ 100 Kartoffeln/ 100 g Kartoffeln",
"Shopping_input_placeholder": "z.B. 100 g Kartoffeln",
"Shopping_list": "Einkaufsliste",
"ShowDelayed": "Zeige verschobene Elemente",
"ShowIngredients": "Zutaten anzeigen",
@@ -616,6 +617,8 @@
"Unrated": "Unbewertet",
"Up": "Hoch",
"Update": "Aktualisieren",
"UpdateFoodLists": "Lebensmittel Einkaufslisten aktualisieren",
"UpdateFoodListsHelp": "Aktualisiert die Standardeinkaufslisten im Lebensmittel wenn diese beim Einkaufen geändert werden.",
"Update_Existing_Data": "Vorhandene Daten aktualisieren",
"Updated": "Aktualisiert",
"UpgradeNow": "Jetzt Upgraden",

View File

@@ -150,6 +150,7 @@
"Decimals": "Decimals",
"Default": "Default",
"DefaultPage": "Default Page",
"DefaultShoppingListHelp": "Default List when this Food is added to the Shoppinglist.",
"Default_Unit": "Default Unit",
"DelayFor": "Delay for {hours} hours",
"DelayUntil": "Delay Until",
@@ -512,7 +513,7 @@
"Shopping_Categories": "Shopping Categories",
"Shopping_Category": "Shopping Category",
"Shopping_List_Empty": "Your shopping list is currently empty, you can add items via the context menu of a meal plan entry (right click on the card or left click the menu icon)",
"Shopping_input_placeholder": "e.g. Potato/100 Potatoes/100 g Potatoes",
"Shopping_input_placeholder": "e.g. 100 g Potatoes",
"Shopping_list": "Shopping List",
"ShowDelayed": "Show delayed items",
"ShowIngredients": "Show Ingredients",
@@ -614,6 +615,8 @@
"Unrated": "Unrated",
"Up": "Up",
"Update": "Update",
"UpdateFoodLists": "Update Food Shoppinglists",
"UpdateFoodListsHelp": "Update the default shopping lists in the food when changing shopping lists during shopping.",
"Update_Existing_Data": "Update Existing Data",
"Updated": "Updated",
"UpgradeNow": "Upgrade now",

View File

@@ -1972,7 +1972,7 @@ export interface ApiShoppingListCascadingListRequest {
}
export interface ApiShoppingListCreateRequest {
shoppingList?: Omit<ShoppingList, 'createdAt'|'updatedAt'>;
shoppingList?: ShoppingList;
}
export interface ApiShoppingListDestroyRequest {
@@ -2026,7 +2026,7 @@ export interface ApiShoppingListNullingListRequest {
export interface ApiShoppingListPartialUpdateRequest {
id: number;
patchedShoppingList?: Omit<PatchedShoppingList, 'createdAt'|'updatedAt'>;
patchedShoppingList?: PatchedShoppingList;
}
export interface ApiShoppingListProtectingListRequest {
@@ -2075,7 +2075,7 @@ export interface ApiShoppingListRetrieveRequest {
export interface ApiShoppingListUpdateRequest {
id: number;
shoppingList?: Omit<ShoppingList, 'createdAt'|'updatedAt'>;
shoppingList?: ShoppingList;
}
export interface ApiSpaceCreateRequest {

View File

@@ -13,6 +13,12 @@
*/
import { mapValues } from '../runtime';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
import type { SupermarketCategory } from './SupermarketCategory';
import {
SupermarketCategoryFromJSON,
@@ -235,6 +241,12 @@ export interface Food {
* @memberof Food
*/
openDataSlug?: string;
/**
*
* @type {Array<ShoppingList>}
* @memberof Food
*/
shoppingLists?: Array<ShoppingList>;
}
/**
@@ -284,6 +296,7 @@ export function FoodFromJSONTyped(json: any, ignoreDiscriminator: boolean): Food
'substituteOnhand': json['substitute_onhand'],
'childInheritFields': json['child_inherit_fields'] == null ? undefined : ((json['child_inherit_fields'] as Array<any>).map(FoodInheritFieldFromJSON)),
'openDataSlug': json['open_data_slug'] == null ? undefined : json['open_data_slug'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
};
}
@@ -312,6 +325,7 @@ export function FoodToJSON(value?: Omit<Food, 'shopping'|'parent'|'numchild'|'fu
'substitute_children': value['substituteChildren'],
'child_inherit_fields': value['childInheritFields'] == null ? undefined : ((value['childInheritFields'] as Array<any>).map(FoodInheritFieldToJSON)),
'open_data_slug': value['openDataSlug'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
};
}

View File

@@ -13,6 +13,12 @@
*/
import { mapValues } from '../runtime';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
import type { SupermarketCategory } from './SupermarketCategory';
import {
SupermarketCategoryFromJSON,
@@ -235,6 +241,12 @@ export interface PatchedFood {
* @memberof PatchedFood
*/
openDataSlug?: string;
/**
*
* @type {Array<ShoppingList>}
* @memberof PatchedFood
*/
shoppingLists?: Array<ShoppingList>;
}
/**
@@ -278,6 +290,7 @@ export function PatchedFoodFromJSONTyped(json: any, ignoreDiscriminator: boolean
'substituteOnhand': json['substitute_onhand'] == null ? undefined : json['substitute_onhand'],
'childInheritFields': json['child_inherit_fields'] == null ? undefined : ((json['child_inherit_fields'] as Array<any>).map(FoodInheritFieldFromJSON)),
'openDataSlug': json['open_data_slug'] == null ? undefined : json['open_data_slug'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
};
}
@@ -306,6 +319,7 @@ export function PatchedFoodToJSON(value?: Omit<PatchedFood, 'shopping'|'parent'|
'substitute_children': value['substituteChildren'],
'child_inherit_fields': value['childInheritFields'] == null ? undefined : ((value['childInheritFields'] as Array<any>).map(FoodInheritFieldToJSON)),
'open_data_slug': value['openDataSlug'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
};
}

View File

@@ -43,18 +43,6 @@ export interface PatchedShoppingList {
* @memberof PatchedShoppingList
*/
color?: string;
/**
*
* @type {Date}
* @memberof PatchedShoppingList
*/
readonly createdAt?: Date;
/**
*
* @type {Date}
* @memberof PatchedShoppingList
*/
readonly updatedAt?: Date;
}
/**
@@ -78,12 +66,10 @@ export function PatchedShoppingListFromJSONTyped(json: any, ignoreDiscriminator:
'name': json['name'] == null ? undefined : json['name'],
'description': json['description'] == null ? undefined : json['description'],
'color': json['color'] == null ? undefined : json['color'],
'createdAt': json['created_at'] == null ? undefined : (new Date(json['created_at'])),
'updatedAt': json['updated_at'] == null ? undefined : (new Date(json['updated_at'])),
};
}
export function PatchedShoppingListToJSON(value?: Omit<PatchedShoppingList, 'createdAt'|'updatedAt'> | null): any {
export function PatchedShoppingListToJSON(value?: PatchedShoppingList | null): any {
if (value == null) {
return value;
}

View File

@@ -37,12 +37,12 @@ import {
UnitFromJSONTyped,
UnitToJSON,
} from './Unit';
import type { Food } from './Food';
import type { FoodSimple } from './FoodSimple';
import {
FoodFromJSON,
FoodFromJSONTyped,
FoodToJSON,
} from './Food';
FoodSimpleFromJSON,
FoodSimpleFromJSONTyped,
FoodSimpleToJSON,
} from './FoodSimple';
/**
* Adds nested create feature
@@ -70,10 +70,10 @@ export interface PatchedShoppingListEntry {
shoppingLists?: Array<ShoppingList>;
/**
*
* @type {Food}
* @type {FoodSimple}
* @memberof PatchedShoppingListEntry
*/
food?: Food;
food?: FoodSimple;
/**
*
* @type {Unit}
@@ -168,7 +168,7 @@ export function PatchedShoppingListEntryFromJSONTyped(json: any, ignoreDiscrimin
'id': json['id'] == null ? undefined : json['id'],
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
'food': json['food'] == null ? undefined : FoodFromJSON(json['food']),
'food': json['food'] == null ? undefined : FoodSimpleFromJSON(json['food']),
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
'amount': json['amount'] == null ? undefined : json['amount'],
'order': json['order'] == null ? undefined : json['order'],
@@ -193,7 +193,7 @@ export function PatchedShoppingListEntryToJSON(value?: Omit<PatchedShoppingListE
'id': value['id'],
'list_recipe': value['listRecipe'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
'food': FoodToJSON(value['food']),
'food': FoodSimpleToJSON(value['food']),
'unit': UnitToJSON(value['unit']),
'amount': value['amount'],
'order': value['order'],

View File

@@ -62,18 +62,18 @@ export interface PatchedShoppingListRecipe {
* @memberof PatchedShoppingListRecipe
*/
readonly recipeData?: RecipeOverview;
/**
*
* @type {number}
* @memberof PatchedShoppingListRecipe
*/
mealplan?: number;
/**
*
* @type {MealPlan}
* @memberof PatchedShoppingListRecipe
*/
readonly mealPlanData?: MealPlan;
/**
*
* @type {number}
* @memberof PatchedShoppingListRecipe
*/
mealplan?: number;
/**
*
* @type {number}
@@ -109,8 +109,8 @@ export function PatchedShoppingListRecipeFromJSONTyped(json: any, ignoreDiscrimi
'name': json['name'] == null ? undefined : json['name'],
'recipe': json['recipe'] == null ? undefined : json['recipe'],
'recipeData': json['recipe_data'] == null ? undefined : RecipeOverviewFromJSON(json['recipe_data']),
'mealplan': json['mealplan'] == null ? undefined : json['mealplan'],
'mealPlanData': json['meal_plan_data'] == null ? undefined : MealPlanFromJSON(json['meal_plan_data']),
'mealplan': json['mealplan'] == null ? undefined : json['mealplan'],
'servings': json['servings'] == null ? undefined : json['servings'],
'createdBy': json['created_by'] == null ? undefined : UserFromJSON(json['created_by']),
};

View File

@@ -19,6 +19,12 @@ import {
SupermarketCategoryRelationFromJSONTyped,
SupermarketCategoryRelationToJSON,
} from './SupermarketCategoryRelation';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
/**
* Moves `UniqueValidator`'s from the validation stage to the save stage.
@@ -78,6 +84,12 @@ export interface PatchedSupermarket {
* @memberof PatchedSupermarket
*/
description?: string;
/**
*
* @type {Array<ShoppingList>}
* @memberof PatchedSupermarket
*/
shoppingLists?: Array<ShoppingList>;
/**
*
* @type {Array<SupermarketCategoryRelation>}
@@ -112,6 +124,7 @@ export function PatchedSupermarketFromJSONTyped(json: any, ignoreDiscriminator:
'id': json['id'] == null ? undefined : json['id'],
'name': json['name'] == null ? undefined : json['name'],
'description': json['description'] == null ? undefined : json['description'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
'categoryToSupermarket': json['category_to_supermarket'] == null ? undefined : ((json['category_to_supermarket'] as Array<any>).map(SupermarketCategoryRelationFromJSON)),
'openDataSlug': json['open_data_slug'] == null ? undefined : json['open_data_slug'],
};
@@ -126,6 +139,7 @@ export function PatchedSupermarketToJSON(value?: Omit<PatchedSupermarket, 'categ
'id': value['id'],
'name': value['name'],
'description': value['description'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
'open_data_slug': value['openDataSlug'],
};
}

View File

@@ -200,6 +200,12 @@ export interface PatchedUserPreference {
* @memberof PatchedUserPreference
*/
csvPrefix?: string;
/**
*
* @type {boolean}
* @memberof PatchedUserPreference
*/
shoppingUpdateFoodLists?: boolean;
/**
*
* @type {boolean}
@@ -273,6 +279,7 @@ export function PatchedUserPreferenceFromJSONTyped(json: any, ignoreDiscriminato
'shoppingRecentDays': json['shopping_recent_days'] == null ? undefined : json['shopping_recent_days'],
'csvDelim': json['csv_delim'] == null ? undefined : json['csv_delim'],
'csvPrefix': json['csv_prefix'] == null ? undefined : json['csv_prefix'],
'shoppingUpdateFoodLists': json['shopping_update_food_lists'] == null ? undefined : json['shopping_update_food_lists'],
'filterToSupermarket': json['filter_to_supermarket'] == null ? undefined : json['filter_to_supermarket'],
'shoppingAddOnhand': json['shopping_add_onhand'] == null ? undefined : json['shopping_add_onhand'],
'leftHanded': json['left_handed'] == null ? undefined : json['left_handed'],
@@ -309,6 +316,7 @@ export function PatchedUserPreferenceToJSON(value?: Omit<PatchedUserPreference,
'shopping_recent_days': value['shoppingRecentDays'],
'csv_delim': value['csvDelim'],
'csv_prefix': value['csvPrefix'],
'shopping_update_food_lists': value['shoppingUpdateFoodLists'],
'filter_to_supermarket': value['filterToSupermarket'],
'shopping_add_onhand': value['shoppingAddOnhand'],
'left_handed': value['leftHanded'],

View File

@@ -43,26 +43,12 @@ export interface ShoppingList {
* @memberof ShoppingList
*/
color?: string;
/**
*
* @type {Date}
* @memberof ShoppingList
*/
readonly createdAt: Date;
/**
*
* @type {Date}
* @memberof ShoppingList
*/
readonly updatedAt: Date;
}
/**
* Check if a given object implements the ShoppingList interface.
*/
export function instanceOfShoppingList(value: object): value is ShoppingList {
if (!('createdAt' in value) || value['createdAt'] === undefined) return false;
if (!('updatedAt' in value) || value['updatedAt'] === undefined) return false;
return true;
}
@@ -80,12 +66,10 @@ export function ShoppingListFromJSONTyped(json: any, ignoreDiscriminator: boolea
'name': json['name'] == null ? undefined : json['name'],
'description': json['description'] == null ? undefined : json['description'],
'color': json['color'] == null ? undefined : json['color'],
'createdAt': (new Date(json['created_at'])),
'updatedAt': (new Date(json['updated_at'])),
};
}
export function ShoppingListToJSON(value?: Omit<ShoppingList, 'createdAt'|'updatedAt'> | null): any {
export function ShoppingListToJSON(value?: ShoppingList | null): any {
if (value == null) {
return value;
}

View File

@@ -37,12 +37,12 @@ import {
UnitFromJSONTyped,
UnitToJSON,
} from './Unit';
import type { Food } from './Food';
import type { FoodSimple } from './FoodSimple';
import {
FoodFromJSON,
FoodFromJSONTyped,
FoodToJSON,
} from './Food';
FoodSimpleFromJSON,
FoodSimpleFromJSONTyped,
FoodSimpleToJSON,
} from './FoodSimple';
/**
* Adds nested create feature
@@ -70,10 +70,10 @@ export interface ShoppingListEntry {
shoppingLists?: Array<ShoppingList>;
/**
*
* @type {Food}
* @type {FoodSimple}
* @memberof ShoppingListEntry
*/
food: Food | null;
food: FoodSimple | null;
/**
*
* @type {Unit}
@@ -174,7 +174,7 @@ export function ShoppingListEntryFromJSONTyped(json: any, ignoreDiscriminator: b
'id': json['id'] == null ? undefined : json['id'],
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
'food': FoodFromJSON(json['food']),
'food': FoodSimpleFromJSON(json['food']),
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
'amount': json['amount'],
'order': json['order'] == null ? undefined : json['order'],
@@ -199,7 +199,7 @@ export function ShoppingListEntryToJSON(value?: Omit<ShoppingListEntry, 'listRec
'id': value['id'],
'list_recipe': value['listRecipe'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
'food': FoodToJSON(value['food']),
'food': FoodSimpleToJSON(value['food']),
'unit': UnitToJSON(value['unit']),
'amount': value['amount'],
'order': value['order'],

View File

@@ -62,18 +62,18 @@ export interface ShoppingListRecipe {
* @memberof ShoppingListRecipe
*/
readonly recipeData: RecipeOverview;
/**
*
* @type {number}
* @memberof ShoppingListRecipe
*/
mealplan?: number;
/**
*
* @type {MealPlan}
* @memberof ShoppingListRecipe
*/
readonly mealPlanData: MealPlan;
/**
*
* @type {number}
* @memberof ShoppingListRecipe
*/
mealplan?: number;
/**
*
* @type {number}
@@ -113,8 +113,8 @@ export function ShoppingListRecipeFromJSONTyped(json: any, ignoreDiscriminator:
'name': json['name'] == null ? undefined : json['name'],
'recipe': json['recipe'] == null ? undefined : json['recipe'],
'recipeData': RecipeOverviewFromJSON(json['recipe_data']),
'mealplan': json['mealplan'] == null ? undefined : json['mealplan'],
'mealPlanData': MealPlanFromJSON(json['meal_plan_data']),
'mealplan': json['mealplan'] == null ? undefined : json['mealplan'],
'servings': json['servings'],
'createdBy': UserFromJSON(json['created_by']),
};

View File

@@ -19,6 +19,12 @@ import {
SupermarketCategoryRelationFromJSONTyped,
SupermarketCategoryRelationToJSON,
} from './SupermarketCategoryRelation';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
/**
* Moves `UniqueValidator`'s from the validation stage to the save stage.
@@ -78,6 +84,12 @@ export interface Supermarket {
* @memberof Supermarket
*/
description?: string;
/**
*
* @type {Array<ShoppingList>}
* @memberof Supermarket
*/
shoppingLists?: Array<ShoppingList>;
/**
*
* @type {Array<SupermarketCategoryRelation>}
@@ -114,6 +126,7 @@ export function SupermarketFromJSONTyped(json: any, ignoreDiscriminator: boolean
'id': json['id'] == null ? undefined : json['id'],
'name': json['name'],
'description': json['description'] == null ? undefined : json['description'],
'shoppingLists': json['shopping_lists'] == null ? undefined : ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
'categoryToSupermarket': ((json['category_to_supermarket'] as Array<any>).map(SupermarketCategoryRelationFromJSON)),
'openDataSlug': json['open_data_slug'] == null ? undefined : json['open_data_slug'],
};
@@ -128,6 +141,7 @@ export function SupermarketToJSON(value?: Omit<Supermarket, 'categoryToSupermark
'id': value['id'],
'name': value['name'],
'description': value['description'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)),
'open_data_slug': value['openDataSlug'],
};
}

View File

@@ -200,6 +200,12 @@ export interface UserPreference {
* @memberof UserPreference
*/
csvPrefix?: string;
/**
*
* @type {boolean}
* @memberof UserPreference
*/
shoppingUpdateFoodLists?: boolean;
/**
*
* @type {boolean}
@@ -276,6 +282,7 @@ export function UserPreferenceFromJSONTyped(json: any, ignoreDiscriminator: bool
'shoppingRecentDays': json['shopping_recent_days'] == null ? undefined : json['shopping_recent_days'],
'csvDelim': json['csv_delim'] == null ? undefined : json['csv_delim'],
'csvPrefix': json['csv_prefix'] == null ? undefined : json['csv_prefix'],
'shoppingUpdateFoodLists': json['shopping_update_food_lists'] == null ? undefined : json['shopping_update_food_lists'],
'filterToSupermarket': json['filter_to_supermarket'] == null ? undefined : json['filter_to_supermarket'],
'shoppingAddOnhand': json['shopping_add_onhand'] == null ? undefined : json['shopping_add_onhand'],
'leftHanded': json['left_handed'] == null ? undefined : json['left_handed'],
@@ -312,6 +319,7 @@ export function UserPreferenceToJSON(value?: Omit<UserPreference, 'user'|'foodIn
'shopping_recent_days': value['shoppingRecentDays'],
'csv_delim': value['csvDelim'],
'csv_prefix': value['csvPrefix'],
'shopping_update_food_lists': value['shoppingUpdateFoodLists'],
'filter_to_supermarket': value['filterToSupermarket'],
'shopping_add_onhand': value['shoppingAddOnhand'],
'left_handed': value['leftHanded'],

View File

@@ -1,6 +1,6 @@
import {acceptHMRUpdate, defineStore} from "pinia"
import {ApiApi, ApiShoppingListEntryListRequest, Food, Recipe, ShoppingListEntry, ShoppingListEntryBulk, ShoppingListRecipe, Supermarket, SupermarketCategory} from "@/openapi";
import {computed, ref} from "vue";
import {computed, ref, shallowRef, triggerRef} from "vue";
import {
IShoppingExportEntry,
IShoppingList,
@@ -33,17 +33,18 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let autoSyncHasFocus = ref(true)
let autoSyncTimeoutId = ref(0)
let undoStack = ref([] as ShoppingOperationHistoryEntry[])
let undoStack = shallowRef([] as ShoppingOperationHistoryEntry[])
let queueTimeoutId = ref(-1)
let itemCheckSyncQueue = ref([] as IShoppingSyncQueueEntry[])
let itemCheckSyncQueue = shallowRef([] as IShoppingSyncQueueEntry[])
let syncQueueRunning = ref(false)
let entriesByGroup = shallowRef([] as IShoppingListCategory[])
/**
* build a multi-level data structure ready for display from shopping list entries
* group by selected grouping key
*/
const getEntriesByGroup = computed(() => {
console.log('-> getEntriesByGroup called')
function updateEntriesStructure() {
let structure = {} as IShoppingList
structure.categories = new Map<string, IShoppingListCategory>
@@ -78,17 +79,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
}
})
return orderedStructure
}, {
onTrack(e) {
// triggered when count.value is tracked as a dependency
},
onTrigger(e) {
// triggered when count.value is mutated
console.log('TRIGGER', e)
}
})
entriesByGroup.value = orderedStructure
}
/**
@@ -97,8 +89,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
*/
let totalFoods = computed(() => {
let count = 0
if (initialized.value){
getEntriesByGroup.value.forEach(category => {
if (initialized.value) {
entriesByGroup.value.forEach(category => {
count += category.foods.size
})
}
@@ -113,7 +105,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
function getFlatEntries() {
let items: IShoppingExportEntry[] = []
getEntriesByGroup.value.forEach(shoppingListEntry => {
entriesByGroup.value.forEach(shoppingListEntry => {
shoppingListEntry.foods.forEach(food => {
food.entries.forEach(entry => {
items.push({
@@ -218,6 +210,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
}
Promise.allSettled(promises).then(() => {
updateEntriesStructure()
currentlyUpdating.value = false
initialized.value = true
})
@@ -258,6 +251,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
const api = new ApiApi()
return api.apiShoppingListEntryCreate({shoppingListEntry: object}).then((r) => {
entries.value.set(r.id!, r)
updateEntriesStructure()
if (undo) {
registerChange("CREATE", [r])
}
@@ -290,6 +284,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
})
}
/**
* delete shopping list entry object from DB and store
* @param object entry object to delete
@@ -299,6 +294,19 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
const api = new ApiApi()
return api.apiShoppingListEntryDestroy({id: object.id!}).then((r) => {
entries.value.delete(object.id!)
let categoryName = getEntryCategoryKey(object)
entriesByGroup.value.forEach(category => {
if (category.name == categoryName) {
category.foods.get(object.food!.id!)?.entries.delete(object.id!)
if(category.foods.get(object.food!.id!)?.entries.size == 0) {
category.foods.delete(object.food!.id!)
triggerRef(entriesByGroup)
}
}
})
if (undo) {
registerChange("DESTROY", [object])
}
@@ -322,7 +330,32 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
return recipes
}
// convenience methods
/**
* get the key (name) of the IShoppingListCategory that an entry belongs to
* @param object
*/
function getEntryCategoryKey(object: ShoppingListEntry) {
let group = useUserPreferenceStore().deviceSettings.shopping_selected_grouping
let groupingKey = UNDEFINED_CATEGORY
if (group == ShoppingGroupingOptions.CATEGORY && object.food != null && object.food.supermarketCategory != null) {
groupingKey = object.food?.supermarketCategory?.name
} else if (group == ShoppingGroupingOptions.CREATED_BY) {
groupingKey = object.createdBy.displayName
} else if (group == ShoppingGroupingOptions.RECIPE && object.listRecipeData != null) {
if (object.listRecipeData.recipeData != null) {
groupingKey = object.listRecipeData.recipeData.name
if (object.listRecipeData.mealPlanData != null) {
groupingKey += ' - ' + object.listRecipeData.mealPlanData.mealType.name + ' - ' + DateTime.fromJSDate(object.listRecipeData.mealPlanData.fromDate).toLocaleString(DateTime.DATE_SHORT)
}
}
}
return groupingKey
}
/**
* puts an entry into the appropriate group of the IShoppingList datastructure
* if a group does not yet exist and the sorting is not set to category with selected supermarket only, it will be created
@@ -330,23 +363,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
* @param entry
*/
function updateEntryInStructure(structure: IShoppingList, entry: ShoppingListEntry) {
let groupingKey = UNDEFINED_CATEGORY
let group = useUserPreferenceStore().deviceSettings.shopping_selected_grouping
if (group == ShoppingGroupingOptions.CATEGORY && entry.food != null && entry.food.supermarketCategory != null) {
groupingKey = entry.food?.supermarketCategory?.name
} else if (group == ShoppingGroupingOptions.CREATED_BY) {
groupingKey = entry.createdBy.displayName
} else if (group == ShoppingGroupingOptions.RECIPE && entry.listRecipeData != null) {
if (entry.listRecipeData.recipeData != null) {
groupingKey = entry.listRecipeData.recipeData.name
if (entry.listRecipeData.mealPlanData != null) {
groupingKey += ' - ' + entry.listRecipeData.mealPlanData.mealType.name + ' - ' + DateTime.fromJSDate(entry.listRecipeData.mealPlanData.fromDate).toLocaleString(DateTime.DATE_SHORT)
}
}
}
let groupingKey = getEntryCategoryKey(entry)
if (!structure.categories.has(groupingKey) && !(group == ShoppingGroupingOptions.CATEGORY && useUserPreferenceStore().deviceSettings.shopping_show_selected_supermarket_only)) {
structure.categories.set(groupingKey, {'name': groupingKey, 'foods': new Map<number, IShoppingListFood>} as IShoppingListCategory)
@@ -374,19 +392,18 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
if (undo) {
registerChange((checked ? 'CHECKED' : 'UNCHECKED'), entries)
}
let entryIdList: number[] = []
entries.forEach(entry => {
entry.checked = checked
entryIdList.push(entry.id!)
})
itemCheckSyncQueue.value.push({
ids: entryIdList,
checked: checked,
status: 'waiting',
} as IShoppingSyncQueueEntry)
runSyncQueue(5)
runSyncQueue(100)
}
/**
@@ -401,7 +418,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let promises: Promise<void>[] = []
let updatedEntries = new Map<number, ShoppingListEntry>()
itemCheckSyncQueue.value.forEach((entry, index) => {
entry['status'] = ((entry['status'] === 'waiting_failed_before') ? 'syncing_failed_before' : 'syncing')
syncQueueRunning.value = true
@@ -575,7 +591,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
entries,
supermarkets,
supermarketCategories,
getEntriesByGroup,
updateEntriesStructure,
entriesByGroup,
autoSyncTimeoutId,
autoSyncHasFocus,
autoSyncLastTimestamp,

View File

@@ -228,7 +228,7 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
shopping_show_selected_supermarket_only: false,
shopping_selected_grouping: ShoppingGroupingOptions.CATEGORY,
shopping_selected_supermarket: null,
shopping_selected_shopping_list: null,
shopping_selected_shopping_list: [],
shopping_item_info_created_by: false,
shopping_item_info_mealplan: true,
shopping_item_info_recipe: true,

View File

@@ -23,7 +23,6 @@ export interface IShoppingList {
export interface IShoppingListCategory {
name: string,
foods: Map<number, IShoppingListFood>,
stats: ShoppingListStats,
}
/**

View File

@@ -6,7 +6,7 @@ export type DeviceSettings = {
shopping_show_selected_supermarket_only: boolean
shopping_selected_grouping: string
shopping_selected_supermarket: Supermarket | null
shopping_selected_shopping_list: ShoppingList | null
shopping_selected_shopping_list: number[]
shopping_item_info_created_by: boolean
shopping_item_info_mealplan: boolean
shopping_item_info_recipe: boolean

View File

@@ -19,8 +19,10 @@ export function isEntryVisible(entry: ShoppingListEntry, deviceSettings: DeviceS
entryVisible = false
}
if(deviceSettings.shopping_selected_shopping_list != null && entry.shoppingLists?.findIndex(sl => sl.id == deviceSettings.shopping_selected_shopping_list.id) == -1){
entryVisible = false
if(deviceSettings.shopping_selected_shopping_list.length > 0){
if(!deviceSettings.shopping_selected_shopping_list.some(sl => (entry.shoppingLists?.findIndex(eSl => eSl.id == sl) != -1))){
entryVisible = false
}
}
return entryVisible
}