Compare commits

..

6 Commits

Author SHA1 Message Date
vabene1111
fc40b5220b Merge branch 'develop' 2025-11-24 20:40:07 +01:00
vabene1111
c0d172574d Merge branch 'develop' 2025-11-19 21:53:16 +01:00
vabene1111
84fd3055ea Merge branch 'develop' 2025-11-18 15:29:08 +01:00
vabene1111
d22b5a4a39 Merge branch 'develop' 2025-10-15 15:18:13 +02:00
vabene1111
602f0a8bf0 Merge branch 'master' of https://github.com/TandoorRecipes/recipes 2025-10-11 11:59:11 +02:00
vabene1111
856f417d1b Merge branch 'develop' 2025-10-08 07:57:59 +02:00
81 changed files with 3600 additions and 5086 deletions

View File

@@ -31,7 +31,7 @@ jobs:
pip install -r requirements.txt pip install -r requirements.txt
- name: Cache StaticFiles - name: Cache StaticFiles
uses: actions/cache@v5 uses: actions/cache@v4
id: django_cache id: django_cache
with: with:
path: | path: |
@@ -64,7 +64,7 @@ jobs:
run: | run: |
python3 manage.py collectstatic --noinput python3 manage.py collectstatic --noinput
- uses: actions/cache/save@v5 - uses: actions/cache/save@v4
if: steps.django_cache.outputs.cache-hit != 'true' if: steps.django_cache.outputs.cache-hit != 'true'
with: with:
path: | path: |

View File

@@ -177,9 +177,8 @@ class RecipeShoppingEditor():
existing = self._shopping_list_recipe.entries.filter(ingredient__in=ingredients).values_list('ingredient__pk', flat=True) existing = self._shopping_list_recipe.entries.filter(ingredient__in=ingredients).values_list('ingredient__pk', flat=True)
add_ingredients = ingredients.exclude(id__in=existing) add_ingredients = ingredients.exclude(id__in=existing)
entries = []
for i in [x for x in add_ingredients if x.food]: for i in [x for x in add_ingredients if x.food]:
entry = ShoppingListEntry( ShoppingListEntry.objects.create(
list_recipe=self._shopping_list_recipe, list_recipe=self._shopping_list_recipe,
food=i.food, food=i.food,
unit=i.unit, unit=i.unit,
@@ -188,12 +187,6 @@ class RecipeShoppingEditor():
created_by=self.created_by, created_by=self.created_by,
space=self.space, space=self.space,
) )
entries.append(entry)
ShoppingListEntry.objects.bulk_create(entries)
for e in entries:
if e.food.shopping_lists.count() > 0:
e.shopping_lists.set(e.food.shopping_lists.all())
# deletes shopping list entries not in ingredients list # deletes shopping list entries not in ingredients list
def _delete_ingredients(self, ingredients=None): def _delete_ingredients(self, ingredients=None):

View File

@@ -197,7 +197,7 @@ class Mealie1(Integration):
space=self.request.space, space=self.request.space,
) )
ingredients_relation.append(Step.ingredients.through(step_id=get_step_id(i, first_step_of_recipe_dict, step_id_dict,recipe_ingredient_ref_link_dict), ingredient_id=ingredient.pk)) ingredients_relation.append(Step.ingredients.through(step_id=get_step_id(i, first_step_of_recipe_dict, step_id_dict,recipe_ingredient_ref_link_dict), ingredient_id=ingredient.pk))
elif i['note'] and i['note'].strip(): elif i['note'].strip():
amount, unit, food, note = ingredient_parser.parse(i['note'].strip()) amount, unit, food, note = ingredient_parser.parse(i['note'].strip())
f = ingredient_parser.get_food(food) f = ingredient_parser.get_food(food)
u = ingredient_parser.get_unit(unit) u = ingredient_parser.get_unit(unit)

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 20:15+0200\n" "POT-Creation-Date: 2025-09-22 20:15+0200\n"
"PO-Revision-Date: 2025-12-01 06:08+0000\n" "PO-Revision-Date: 2025-11-15 12:08+0000\n"
"Last-Translator: \"Matjaž T.\" <matjaz@moj-svet.si>\n" "Last-Translator: \"Matjaž T.\" <matjaz@moj-svet.si>\n"
"Language-Team: Slovenian <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Slovenian <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/sl/>\n" "recipes-backend/sl/>\n"
@@ -2129,8 +2129,9 @@ msgid ""
"please consult the django documentation on how to reset passwords." "please consult the django documentation on how to reset passwords."
msgstr "" msgstr ""
"Nastavitveno stran lahko uporabite samo za ustvarjanje prvega " "Nastavitveno stran lahko uporabite samo za ustvarjanje prvega "
"uporabnika! Če ste pozabili poverilnice superuporabnika, " "uporabnika! \n"
"si oglejte dokumentacijo django za ponastavitev gesel." " Če ste pozabili svoje poverilnice superuporabnika, si oglejte "
"dokumentacijo django o tem, kako ponastaviti gesla."
#: .\cookbook\views\views.py:304 #: .\cookbook\views\views.py:304
msgid "Passwords dont match!" msgid "Passwords dont match!"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-08-01 15:04+0200\n" "POT-Creation-Date: 2024-08-01 15:04+0200\n"
"PO-Revision-Date: 2025-12-02 08:03+0000\n" "PO-Revision-Date: 2025-11-22 20:03+0000\n"
"Last-Translator: SerhiiOS <serhios@users.noreply.translate.tandoor.dev>\n" "Last-Translator: SerhiiOS <serhios@users.noreply.translate.tandoor.dev>\n"
"Language-Team: Ukrainian <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Ukrainian <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/uk/>\n" "recipes-backend/uk/>\n"
@@ -468,7 +468,7 @@ msgstr "Пошук"
#: .\cookbook\models.py:455 .\cookbook\templates\base.html:114 #: .\cookbook\models.py:455 .\cookbook\templates\base.html:114
#: .\cookbook\templates\meal_plan.html:7 #: .\cookbook\templates\meal_plan.html:7
msgid "Meal-Plan" msgid "Meal-Plan"
msgstr "Меню" msgstr "План харчування"
#: .\cookbook\models.py:456 .\cookbook\templates\base.html:122 #: .\cookbook\models.py:456 .\cookbook\templates\base.html:122
#: .\cookbook\views\views.py:459 #: .\cookbook\views\views.py:459
@@ -2790,7 +2790,7 @@ msgstr ""
#: .\cookbook\views\views.py:451 #: .\cookbook\views\views.py:451
msgid "Manage recipes, shopping list, meal plans and more." msgid "Manage recipes, shopping list, meal plans and more."
msgstr "Керуйте рецептами, списком покупок, меню тощо." msgstr "Керуйте рецептами, списком покупок, планами харчування тощо."
#: .\cookbook\views\views.py:458 #: .\cookbook\views\views.py:458
msgid "Plan" msgid "Plan"

View File

@@ -1,29 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-30 10:19
import cookbook.models
import django.db.models.deletion
import django_prometheus.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0231_alter_aiprovider_options_alter_automation_options_and_more'),
]
operations = [
migrations.CreateModel(
name='ShoppingList',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, default='', max_length=32)),
('description', models.TextField(blank=True)),
('color', models.CharField(blank=True, max_length=7, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('shopping_list'), models.Model, cookbook.models.PermissionModelMixin),
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 5.2.7 on 2025-11-30 14:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0232_shoppinglist'),
]
operations = [
migrations.AddField(
model_name='food',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
migrations.AddField(
model_name='shoppinglistentry',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
migrations.AddField(
model_name='supermarket',
name='shopping_lists',
field=models.ManyToManyField(blank=True, to='cookbook.shoppinglist'),
),
]

View File

@@ -1,22 +0,0 @@
# 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,7 +551,6 @@ class UserPreference(models.Model, PermissionModelMixin):
show_step_ingredients = models.BooleanField(default=True) show_step_ingredients = models.BooleanField(default=True)
default_delay = models.DecimalField(default=4, max_digits=8, decimal_places=4) default_delay = models.DecimalField(default=4, max_digits=8, decimal_places=4)
shopping_recent_days = models.PositiveIntegerField(default=7) shopping_recent_days = models.PositiveIntegerField(default=7)
shopping_update_food_lists = models.BooleanField(default=True)
csv_delim = models.CharField(max_length=2, default=",") csv_delim = models.CharField(max_length=2, default=",")
csv_prefix = models.CharField(max_length=10, blank=True, ) csv_prefix = models.CharField(max_length=10, blank=True, )
@@ -667,7 +666,6 @@ class Supermarket(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)]) name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation') categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation')
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None) open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
@@ -782,7 +780,6 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL) recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
url = models.CharField(max_length=1024, blank=True, null=True, default='') url = models.CharField(max_length=1024, blank=True, null=True, default='')
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL) # inherited field
shopping_lists = models.ManyToManyField("ShoppingList", blank=True)
ignore_shopping = models.BooleanField(default=False) # inherited field ignore_shopping = models.BooleanField(default=False) # inherited field
onhand_users = models.ManyToManyField(User, blank=True) onhand_users = models.ManyToManyField(User, blank=True)
description = models.TextField(default='', blank=True) description = models.TextField(default='', blank=True)
@@ -946,8 +943,8 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
# def __str__(self): def __str__(self):
# return f'{self.pk}: {self.amount} ' + (self.food.name if self.food else ' ') + (self.unit.name if self.unit else '') return f'{self.pk}: {self.amount} ' + (self.food.name if self.food else ' ') + (self.unit.name if self.unit else '')
class Meta: class Meta:
ordering = ['order', 'pk'] ordering = ['order', 'pk']
@@ -1300,30 +1297,14 @@ class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), mod
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
# def __str__(self): def __str__(self):
# return f'Shopping list recipe {self.id} - {self.recipe}' return f'Shopping list recipe {self.id} - {self.recipe}'
class Meta:
ordering = ('pk',)
class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=32, blank=True, default='')
description = models.TextField(blank=True)
color = models.CharField(max_length=7, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
class Meta: class Meta:
ordering = ('pk',) ordering = ('pk',)
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin): class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
shopping_lists = models.ManyToManyField(ShoppingList, blank=True)
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries') list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries') food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries')
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True) unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)

View File

@@ -1,4 +1,3 @@
import traceback
import uuid import uuid
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal from decimal import Decimal
@@ -38,7 +37,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
ShareLink, ShoppingListEntry, ShoppingListRecipe, Space, ShareLink, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory, Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion, SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig, SearchPreference, SearchFields, AiLog, AiProvider, ShoppingList) UserFile, UserPreference, UserSpace, ViewLog, ConnectorConfig, SearchPreference, SearchFields, AiLog, AiProvider)
from cookbook.templatetags.custom_tags import markdown from cookbook.templatetags.custom_tags import markdown
from recipes.settings import AWS_ENABLED, MEDIA_URL, EMAIL_HOST from recipes.settings import AWS_ENABLED, MEDIA_URL, EMAIL_HOST
@@ -187,35 +186,9 @@ class SpaceFilterSerializer(serializers.ListSerializer):
if isinstance(self.context['request'].user, AnonymousUser): if isinstance(self.context['request'].user, AnonymousUser):
data = [] data = []
else: else:
iterable = data.all() if hasattr(data, 'all') else data
if isinstance(iterable, list) or (isinstance(iterable, QuerySet) and getattr(iterable, '_result_cache', None) is not None):
try:
new_data = []
for u in iterable:
for us in u.userspace_set.all():
if us.space.id == self.context['request'].space.id:
new_data.append(u)
data = new_data
except Exception:
traceback.print_exc()
data = data.filter(userspace__space=self.context['request'].user.get_active_space()).all()
else:
if hasattr(self.context['request'], 'space'):
data = data.filter(userspace__space=self.context['request'].space).all()
else:
# not sure why but this branch can be hit (just normal page load, need to see why)
data = data.filter(userspace__space=self.context['request'].user.get_active_space()).all() data = data.filter(userspace__space=self.context['request'].user.get_active_space()).all()
elif isinstance(data, list): elif isinstance(data, list):
data = [d for d in data if getattr(d, self.child.Meta.model.get_space_key()[0]) == self.context['request'].space] data = [d for d in data if getattr(d, self.child.Meta.model.get_space_key()[0]) == self.context['request'].space]
else:
iterable = data.all() if hasattr(data, 'all') else data
if isinstance(iterable, list) or (isinstance(iterable, QuerySet) and getattr(iterable, '_result_cache', None) is not None):
keys = self.child.Meta.model.get_space_key()
if keys == ('space',):
data = [d for d in iterable if getattr(d, 'space_id') == self.context['request'].space.id]
else:
# use cached results here too, just dont have time to test this now, probably obj.get_space()
data = data.filter(**{'__'.join(self.child.Meta.model.get_space_key()): self.context['request'].space})
else: else:
data = data.filter(**{'__'.join(self.child.Meta.model.get_space_key()): self.context['request'].space}) data = data.filter(**{'__'.join(self.child.Meta.model.get_space_key()): self.context['request'].space})
return super().to_representation(data) return super().to_representation(data)
@@ -511,20 +484,6 @@ class SpacedModelSerializer(serializers.ModelSerializer):
return super().create(validated_data) 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): class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
def create(self, validated_data): def create(self, validated_data):
@@ -574,7 +533,7 @@ class UserPreferenceSerializer(WritableNestedModelSerializer):
'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping',
'food_inherit_default', 'default_delay', 'food_inherit_default', 'default_delay',
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', 'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days',
'csv_delim', 'csv_prefix', 'shopping_update_food_lists', 'csv_delim', 'csv_prefix',
'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'show_step_ingredients', 'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'show_step_ingredients',
'food_children_exist' 'food_children_exist'
) )
@@ -689,7 +648,7 @@ class KeywordLabelSerializer(serializers.ModelSerializer):
@extend_schema_field(str) @extend_schema_field(str)
def get_label(self, obj): def get_label(self, obj):
return obj.name return str(obj)
class Meta: class Meta:
list_serializer_class = SpaceFilterSerializer list_serializer_class = SpaceFilterSerializer
@@ -706,7 +665,7 @@ class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
@extend_schema_field(str) @extend_schema_field(str)
def get_label(self, obj): def get_label(self, obj):
return obj.name return str(obj)
def create(self, validated_data): def create(self, validated_data):
# since multi select tags dont have id's # since multi select tags dont have id's
@@ -781,9 +740,8 @@ class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
fields = ('id', 'category', 'supermarket', 'order') fields = ('id', 'category', 'supermarket', 'order')
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, WritableNestedModelSerializer, OpenDataModelMixin): class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataModelMixin):
category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True) category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True)
shopping_lists = ShoppingListSerializer(many=True, required=False)
def create(self, validated_data): def create(self, validated_data):
validated_data['name'] = validated_data['name'].strip() validated_data['name'] = validated_data['name'].strip()
@@ -794,7 +752,7 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, WritableNe
class Meta: class Meta:
model = Supermarket model = Supermarket
fields = ('id', 'name', 'description', 'shopping_lists', 'category_to_supermarket', 'open_data_slug') fields = ('id', 'name', 'description', 'category_to_supermarket', 'open_data_slug')
class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin): class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin):
@@ -878,7 +836,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
substitute_onhand = serializers.SerializerMethodField('get_substitute_onhand') substitute_onhand = serializers.SerializerMethodField('get_substitute_onhand')
substitute = FoodSimpleSerializer(many=True, allow_null=True, required=False) substitute = FoodSimpleSerializer(many=True, allow_null=True, required=False)
parent = IntegerField(read_only=True) parent = IntegerField(read_only=True)
shopping_lists = ShoppingListSerializer(many=True, required=False)
properties = PropertySerializer(many=True, allow_null=True, required=False) properties = PropertySerializer(many=True, allow_null=True, required=False)
properties_food_unit = UnitSerializer(allow_null=True, required=False) properties_food_unit = UnitSerializer(allow_null=True, required=False)
properties_food_amount = CustomDecimalField(required=False) properties_food_amount = CustomDecimalField(required=False)
@@ -989,7 +947,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
fields = ( fields = (
'id', 'name', 'plural_name', 'description', 'shopping', 'recipe', 'url', 'properties', 'properties_food_amount', 'properties_food_unit', 'fdc_id', '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', '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', 'shopping_lists', 'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug',
) )
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe') read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
@@ -1369,7 +1327,7 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
@extend_schema_field(bool) @extend_schema_field(bool)
def in_shopping(self, obj): def in_shopping(self, obj):
return obj.shoppinglistrecipe_set.count() > 0 return ShoppingListRecipe.objects.filter(mealplan=obj.id).exists()
def create(self, validated_data): def create(self, validated_data):
validated_data['created_by'] = self.context['request'].user validated_data['created_by'] = self.context['request'].user
@@ -1435,23 +1393,13 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ShoppingListRecipe model = ShoppingListRecipe
fields = ('id', 'name', 'recipe', 'recipe_data', 'meal_plan_data', 'mealplan', 'servings', 'created_by',) fields = ('id', 'name', 'recipe', 'recipe_data', 'mealplan', 'meal_plan_data', 'servings', 'created_by',)
read_only_fields = ('id', 'created_by',) read_only_fields = ('id', 'created_by',)
class FoodShoppingSerializer(serializers.ModelSerializer):
supermarket_category = SupermarketCategorySerializer(read_only=True)
shopping_lists = ShoppingListSerializer(read_only=True, many=True)
class Meta:
model = Food
fields = ('id', 'name', 'plural_name', 'supermarket_category', 'shopping_lists')
class ShoppingListEntrySerializer(WritableNestedModelSerializer): class ShoppingListEntrySerializer(WritableNestedModelSerializer):
food = FoodShoppingSerializer(allow_null=True) food = FoodSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True, required=False) unit = UnitSerializer(allow_null=True, required=False)
shopping_lists = ShoppingListSerializer(many=True, required=False)
list_recipe_data = ShoppingListRecipeSerializer(source='list_recipe', read_only=True) list_recipe_data = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
amount = CustomDecimalField() amount = CustomDecimalField()
created_by = UserSerializer(read_only=True) created_by = UserSerializer(read_only=True)
@@ -1500,13 +1448,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
created_by=self.context['request'].user) created_by=self.context['request'].user)
del validated_data['mealplan_id'] del validated_data['mealplan_id']
obj = super().create(validated_data) return 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): def update(self, instance, validated_data):
user = self.context['request'].user user = self.context['request'].user
@@ -1526,7 +1468,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = ShoppingListEntry model = ShoppingListEntry
fields = ( fields = (
'id', 'list_recipe', 'shopping_lists', 'food', 'unit', 'amount', 'order', 'checked', 'ingredient', 'id', 'list_recipe', 'food', 'unit', 'amount', 'order', 'checked', 'ingredient',
'list_recipe_data', 'created_by', 'created_at', 'updated_at', 'completed_at', 'delay_until', 'mealplan_id' 'list_recipe_data', 'created_by', 'created_at', 'updated_at', 'completed_at', 'delay_until', 'mealplan_id'
) )
read_only_fields = ('id', 'created_by', 'created_at') read_only_fields = ('id', 'created_by', 'created_at')
@@ -1787,7 +1729,6 @@ class GenericModelReferenceSerializer(serializers.Serializer):
model = serializers.CharField() model = serializers.CharField()
name = serializers.CharField() name = serializers.CharField()
# Export/Import Serializers # Export/Import Serializers
class KeywordExportSerializer(KeywordSerializer): class KeywordExportSerializer(KeywordSerializer):

View File

@@ -40,7 +40,6 @@ router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
router.register(r'unit-conversion', api.UnitConversionViewSet) router.register(r'unit-conversion', api.UnitConversionViewSet)
router.register(r'property-type', api.PropertyTypeViewSet) # NOTE: if regenerating the legacy API these need renamed to food-property router.register(r'property-type', api.PropertyTypeViewSet) # NOTE: if regenerating the legacy API these need renamed to food-property
router.register(r'property', api.PropertyViewSet) router.register(r'property', api.PropertyViewSet)
router.register(r'shopping-list', api.ShoppingListViewSet)
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet) router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet) router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet)
router.register(r'space', api.SpaceViewSet) router.register(r'space', api.SpaceViewSet)

View File

@@ -88,7 +88,7 @@ from cookbook.models import (Automation, BookmarkletImport, ConnectorConfig, Coo
RecipeBookEntry, ShareLink, ShoppingListEntry, RecipeBookEntry, ShareLink, ShoppingListEntry,
ShoppingListRecipe, Space, Step, Storage, Supermarket, SupermarketCategory, ShoppingListRecipe, Space, Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion, SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog, RecipeImport, SearchPreference, SearchFields, AiLog, AiProvider, ShoppingList UserFile, UserPreference, UserSpace, ViewLog, RecipeImport, SearchPreference, SearchFields, AiLog, AiProvider
) )
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local from cookbook.provider.local import Local
@@ -114,7 +114,7 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer, Au
LocalizationSerializer, ServerSettingsSerializer, RecipeFromSourceResponseSerializer, ShoppingListEntryBulkCreateSerializer, FdcQuerySerializer, LocalizationSerializer, ServerSettingsSerializer, RecipeFromSourceResponseSerializer, ShoppingListEntryBulkCreateSerializer, FdcQuerySerializer,
AiImportSerializer, ImportOpenDataSerializer, ImportOpenDataMetaDataSerializer, ImportOpenDataResponseSerializer, ExportRequestSerializer, AiImportSerializer, ImportOpenDataSerializer, ImportOpenDataMetaDataSerializer, ImportOpenDataResponseSerializer, ExportRequestSerializer,
RecipeImportSerializer, ConnectorConfigSerializer, SearchPreferenceSerializer, SearchFieldsSerializer, RecipeBatchUpdateSerializer, RecipeImportSerializer, ConnectorConfigSerializer, SearchPreferenceSerializer, SearchFieldsSerializer, RecipeBatchUpdateSerializer,
AiProviderSerializer, AiLogSerializer, FoodBatchUpdateSerializer, GenericModelReferenceSerializer, ShoppingListSerializer AiProviderSerializer, AiLogSerializer, FoodBatchUpdateSerializer, GenericModelReferenceSerializer
) )
from cookbook.version_info import TANDOOR_VERSION from cookbook.version_info import TANDOOR_VERSION
from cookbook.views.import_export import get_integration from cookbook.views.import_export import get_integration
@@ -1992,7 +1992,8 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
if serializer.is_valid(): if serializer.is_valid():
entries = [] entries = []
for e in serializer.validated_data['entries']: for e in serializer.validated_data['entries']:
entry = ShoppingListEntry( entries.append(
ShoppingListEntry(
list_recipe_id=obj.pk, list_recipe_id=obj.pk,
amount=e['amount'], amount=e['amount'],
unit_id=e['unit_id'], unit_id=e['unit_id'],
@@ -2001,30 +2002,15 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
created_by_id=request.user.id, created_by_id=request.user.id,
space_id=request.space.id, space_id=request.space.id,
) )
entries.append(entry) )
ShoppingListEntry.objects.bulk_create(entries) ShoppingListEntry.objects.bulk_create(entries)
for e in entries:
if e.food.shopping_lists.count() > 0:
e.shopping_lists.set(e.food.shopping_lists.all())
ConnectorManager.add_work(ActionType.CREATED, *entries) ConnectorManager.add_work(ActionType.CREATED, *entries)
return Response(serializer.validated_data) return Response(serializer.validated_data)
else: else:
return Response(serializer.errors, 400) return Response(serializer.errors, 400)
class ShoppingListViewSet(LoggingMixin, viewsets.ModelViewSet, DeleteRelationMixing):
queryset = ShoppingList.objects
serializer_class = ShoppingListSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
pagination_class = DefaultPagination
def get_queryset(self):
queryset = self.queryset.filter(space=self.request.space).all()
return queryset
@extend_schema_view(list=extend_schema(parameters=[ @extend_schema_view(list=extend_schema(parameters=[
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.'),
@@ -2044,23 +2030,19 @@ class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space) self.queryset = self.queryset.filter(space=self.request.space)
# select_related("list_recipe")
self.queryset = self.queryset.filter( self.queryset = self.queryset.filter(
Q(created_by=self.request.user) Q(created_by=self.request.user)
| Q(created_by__in=list(self.request.user.get_shopping_share()))).prefetch_related('created_by', | Q(created_by__in=list(self.request.user.get_shopping_share()))).prefetch_related('created_by', 'food',
'food', 'food__properties',
'food__shopping_lists', 'food__properties__property_type',
'shopping_lists', 'food__inherit_fields',
'unit', 'food__supermarket_category',
'list_recipe', 'food__onhand_users',
'list_recipe__recipe__keywords', 'food__substitute',
'list_recipe__recipe__created_by', 'food__child_inherit_fields',
'unit', 'list_recipe',
'list_recipe__mealplan', 'list_recipe__mealplan',
'list_recipe__mealplan__shared',
'list_recipe__mealplan__shared__userspace_set',
'list_recipe__mealplan__shoppinglistrecipe_set',
'list_recipe__mealplan__recipe', 'list_recipe__mealplan__recipe',
'list_recipe__mealplan__recipe__keywords',
).distinct().all() ).distinct().all()
updated_after = self.request.query_params.get('updated_after', None) updated_after = self.request.query_params.get('updated_after', None)

View File

@@ -1,4 +1,4 @@
Django==5.2.9 Django==5.2.8
cryptography===45.0.5 cryptography===45.0.5
django-annoying==0.10.6 django-annoying==0.10.6
django-cleanup==9.0.0 django-cleanup==9.0.0

View File

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

View File

@@ -8,9 +8,6 @@
<v-label>{{ $t('Choose_Category') }}</v-label> <v-label>{{ $t('Choose_Category') }}</v-label>
<model-select model="SupermarketCategory" @update:modelValue="categoryUpdate" allow-create></model-select> <model-select model="SupermarketCategory" @update:modelValue="categoryUpdate" allow-create></model-select>
<v-label>{{ $t('ShoppingList') }}</v-label>
<model-select model="ShoppingList" @update:modelValue="shoppingListUpdate" mode="tags" allow-create></model-select>
<v-row> <v-row>
<v-col class="pr-0"> <v-col class="pr-0">
<v-btn height="80px" color="info" density="compact" size="small" block stacked <v-btn height="80px" color="info" density="compact" size="small" block stacked
@@ -79,9 +76,6 @@
<v-list-item-subtitle v-if="isDelayed(e)" class="text-info font-weight-bold"> <v-list-item-subtitle v-if="isDelayed(e)" class="text-info font-weight-bold">
{{ $t('PostponedUntil') }} {{ DateTime.fromJSDate(e.delayUntil!).toLocaleString(DateTime.DATETIME_SHORT) }} {{ $t('PostponedUntil') }} {{ DateTime.fromJSDate(e.delayUntil!).toLocaleString(DateTime.DATETIME_SHORT) }}
</v-list-item-subtitle> </v-list-item-subtitle>
<v-list-item-subtitle v-if="e.shoppingLists.length > 0" class="text-info font-weight-bold">
<shopping-lists-bar :shopping-lists="e.shoppingLists"></shopping-lists-bar>
</v-list-item-subtitle>
<v-btn-group divided border> <v-btn-group divided border>
<v-btn icon="" @click="e.amount = e.amount / 2; updateEntryAmount(e)" v-if="!e.ingredient"> <v-btn icon="" @click="e.amount = e.amount / 2; updateEntryAmount(e)" v-if="!e.ingredient">
@@ -128,8 +122,8 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, ref} from "vue"; import {computed} from "vue";
import {ApiApi, PatchedShoppingListEntry, ShoppingList, ShoppingListEntry, SupermarketCategory} from "@/openapi"; import {ApiApi, PatchedShoppingListEntry, ShoppingListEntry, SupermarketCategory} from "@/openapi";
import ModelSelect from "@/components/inputs/ModelSelect.vue"; import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {IShoppingListFood} from "@/types/Shopping"; import {IShoppingListFood} from "@/types/Shopping";
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue"; import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
@@ -139,16 +133,12 @@ import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
import {useShoppingStore} from "@/stores/ShoppingStore"; import {useShoppingStore} from "@/stores/ShoppingStore";
import {isDelayed, isShoppingListFoodDelayed} from "@/utils/logic_utils"; import {isDelayed, isShoppingListFoodDelayed} from "@/utils/logic_utils";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore"; import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
import ShoppingListsBar from "@/components/display/ShoppingListsBar.vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
const {mobile} = useDisplay() const {mobile} = useDisplay()
const showDialog = defineModel<Boolean>() const showDialog = defineModel<Boolean>()
const shoppingListFood = defineModel<IShoppingListFood>('shoppingListFood', {required: true}) const shoppingListFood = defineModel<IShoppingListFood>('shoppingListFood', {required: true})
const shoppingListUpdateLoading = ref(false)
/** /**
* returns a flat list of entries for the given shopping list food * returns a flat list of entries for the given shopping list food
*/ */
@@ -174,8 +164,6 @@ const isShoppingLineDelayed = computed(() => {
function categoryUpdate(category: SupermarketCategory) { function categoryUpdate(category: SupermarketCategory) {
const api = new ApiApi() const api = new ApiApi()
shoppingListFood.value.food.supermarketCategory = category shoppingListFood.value.food.supermarketCategory = category
shoppingListFood.value.entries.forEach(e => e.food.supermarketCategory = category)
useShoppingStore().updateEntriesStructure()
api.apiFoodUpdate({id: shoppingListFood.value.food.id, food: shoppingListFood.value.food}).then(r => { api.apiFoodUpdate({id: shoppingListFood.value.food.id, food: shoppingListFood.value.food}).then(r => {
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS) useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS)
}).catch(err => { }).catch(err => {
@@ -183,35 +171,6 @@ function categoryUpdate(category: SupermarketCategory) {
}) })
} }
/**
* change the shopping list for all entries
* @param shoppingLists
*/
function shoppingListUpdate(shoppingLists: ShoppingList[]) {
const api = new ApiApi()
const promises: Promise<any>[] = []
shoppingListUpdateLoading.value = true
shoppingListFood.value.entries.forEach(e => {
e.shoppingLists = shoppingLists
promises.push(api.apiShoppingListEntryUpdate({id: e.id, shoppingListEntry: e}).then(r => {
}).catch(err => {
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)
}
/** /**
* add new entry for currently selected food type * add new entry for currently selected food type
*/ */

View File

@@ -1,17 +1,11 @@
<template> <template>
<v-list-item class="swipe-container border-t-sm mt-0 mb-0 pt-0 pb-0 pe-0 pa-0 shopping-border" <v-list-item class="swipe-container border-t-sm mt-0 mb-0 pt-0 pb-0 pe-0 pa-0" :id="itemContainerId" @touchend="handleSwipe()" @click="dialog = true;"
:id="itemContainerId" v-if="isShoppingListFoodVisible(props.shoppingListFood, useUserPreferenceStore().deviceSettings)"
@touchend="handleSwipe()"
@click="dialog = true;"
:value="shoppingListFood"
> >
<!-- <div class="swipe-action" :class="{'bg-success': !isChecked , 'bg-warning': isChecked }">--> <!-- <div class="swipe-action" :class="{'bg-success': !isChecked , 'bg-warning': isChecked }">-->
<!-- <i class="swipe-icon fa-fw fas" :class="{'fa-check': !isChecked , 'fa-cart-plus': isChecked }"></i>--> <!-- <i class="swipe-icon fa-fw fas" :class="{'fa-check': !isChecked , 'fa-cart-plus': isChecked }"></i>-->
<!-- </div>--> <!-- </div>-->
<div class="color-marker-container">
<span :style="{background: sl.color}" v-for="sl in shoppingList"></span>
</div>
<div class="flex-grow-1 p-2"> <div class="flex-grow-1 p-2">
<div class="d-flex"> <div class="d-flex">
@@ -37,18 +31,13 @@
</div> </div>
<template v-slot:[selectBtnSlot]="{ isSelected, select }" v-if="selectEnabled">
<v-list-item-action class="ps-3 pe-3" start>
<v-checkbox-btn :model-value="isSelected" @update:model-value="select" @click.native.stop=""></v-checkbox-btn>
</v-list-item-action>
</template>
<template v-slot:[checkBtnSlot]> <template v-slot:[checkBtnSlot]>
<div class="ps-3 pe-3" @click.native.stop="useShoppingStore().setEntriesCheckedState(entries, !isChecked, true);"> <div class="ps-3 pe-3" @click.native.stop="useShoppingStore().setEntriesCheckedState(entries, !isChecked, true);">
<v-btn color="success" size="large" <v-btn color="success" size="large"
:class="{'btn-success': !isChecked, 'btn-warning': isChecked}" :icon="actionButtonIcon" variant="plain"> :class="{'btn-success': !isChecked, 'btn-warning': isChecked}" :icon="actionButtonIcon" variant="plain">
</v-btn> </v-btn>
</div> </div>
<!-- <i class="d-print-none fa-fw fas" :class="{'fa-check': !isChecked , 'fa-cart-plus': isChecked }"></i>-->
</template> </template>
<!-- <div class="swipe-action bg-primary justify-content-end">--> <!-- <div class="swipe-action bg-primary justify-content-end">-->
@@ -67,23 +56,20 @@ import {computed, PropType, ref} from "vue";
import {DateTime} from "luxon"; import {DateTime} from "luxon";
import {useShoppingStore} from "@/stores/ShoppingStore.js"; import {useShoppingStore} from "@/stores/ShoppingStore.js";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.js"; import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.js";
import {ApiApi, Food, ShoppingList, ShoppingListEntry} from '@/openapi' import {ApiApi, Food, ShoppingListEntry} from '@/openapi'
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore"; import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import {IShoppingListFood, ShoppingLineAmount} from "@/types/Shopping"; import {IShoppingListFood, ShoppingLineAmount} from "@/types/Shopping";
import {isDelayed, isEntryVisible, isShoppingListFoodDelayed, isShoppingListFoodVisible} from "@/utils/logic_utils"; import {isDelayed, isEntryVisible, isShoppingListFoodDelayed, isShoppingListFoodVisible} from "@/utils/logic_utils";
import ShoppingLineItemDialog from "@/components/dialogs/ShoppingLineItemDialog.vue"; import ShoppingLineItemDialog from "@/components/dialogs/ShoppingLineItemDialog.vue";
import {pluralString} from "@/utils/model_utils.ts"; import {pluralString} from "@/utils/model_utils.ts";
import ShoppingListsBar from "@/components/display/ShoppingListsBar.vue";
const emit = defineEmits(['clicked']) const emit = defineEmits(['clicked'])
const props = defineProps({ const props = defineProps({
shoppingListFood: {type: {} as PropType<IShoppingListFood>, required: true}, shoppingListFood: {type: {} as PropType<IShoppingListFood>, required: true},
hideInfoRow: {type: Boolean, default: false}, hideInfoRow: {type: Boolean, default: false}
selectEnabled: {type: Boolean, default: false}
}) })
const checkBtnSlot = ref(useUserPreferenceStore().userSettings.leftHanded ? 'prepend' : 'append') const checkBtnSlot = ref(useUserPreferenceStore().userSettings.leftHanded ? 'prepend' : 'append')
const selectBtnSlot = ref(useUserPreferenceStore().userSettings.leftHanded ? 'append' : 'prepend')
const dialog = ref(false) const dialog = ref(false)
@@ -96,7 +82,9 @@ const entries = computed(() => {
*/ */
const itemContainerId = computed(() => { const itemContainerId = computed(() => {
let id = 'id_sli_' let id = 'id_sli_'
entries.value.forEach(e => id += e.id + '_') for (let i in entries.value) {
id += i + '_'
}
return id return id
}) })
@@ -124,22 +112,6 @@ const actionButtonIcon = computed(() => {
}) })
const shoppingList = computed(() => {
const lists = [] as ShoppingList[]
entries.value.forEach(e => {
if (e.shoppingLists) {
e.shoppingLists.forEach(l => {
if (lists.findIndex(sl => sl.id == l.id) == -1) {
lists.push(l)
}
})
}
})
return lists
})
/** /**
* calculate the amounts for the given line * calculate the amounts for the given line
* can combine 1 to n entries with the same unit * can combine 1 to n entries with the same unit
@@ -151,7 +123,7 @@ const amounts = computed((): ShoppingLineAmount[] => {
for (let i in entries.value) { for (let i in entries.value) {
let e = entries.value[i] let e = entries.value[i]
if (isEntryVisible(e, useUserPreferenceStore().deviceSettings)) {
let unit = -1 let unit = -1
if (e.unit !== undefined && e.unit !== null) { if (e.unit !== undefined && e.unit !== null) {
unit = e.unit.id! unit = e.unit.id!
@@ -178,7 +150,7 @@ const amounts = computed((): ShoppingLineAmount[] => {
} }
} }
} }
}
return unitAmounts return unitAmounts
}) })
@@ -199,6 +171,7 @@ const infoRow = computed(() => {
for (let i in entries.value) { for (let i in entries.value) {
let e = entries.value[i] let e = entries.value[i]
if (isEntryVisible(e, useUserPreferenceStore().deviceSettings)) {
if (authors.indexOf(e.createdBy.displayName) === -1) { if (authors.indexOf(e.createdBy.displayName) === -1) {
authors.push(e.createdBy.displayName) authors.push(e.createdBy.displayName)
@@ -220,7 +193,7 @@ const infoRow = computed(() => {
} }
} }
}
} }
if (useUserPreferenceStore().deviceSettings.shopping_item_info_created_by && authors.length > 0) { if (useUserPreferenceStore().deviceSettings.shopping_item_info_created_by && authors.length > 0) {
@@ -274,22 +247,4 @@ function handleSwipe() {
<style> <style>
/* TODO swipe system classes removed because not working (visually, touch detection was working), retrieve from old ShoppingLineItem VCS */ /* TODO swipe system classes removed because not working (visually, touch detection was working), retrieve from old ShoppingLineItem VCS */
/* 2. Container to wrap the color bars and place them to the far left */
.color-marker-container {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 3px;
display: flex;
flex-direction: column;
}
.color-marker-container span {
width: 100%;
flex-grow: 1;
}
</style> </style>

View File

@@ -2,7 +2,7 @@
<v-tabs v-model="currentTab"> <v-tabs v-model="currentTab">
<v-tab value="shopping"><i class="fas fa-fw" <v-tab value="shopping"><i class="fas fa-fw"
:class="{'fa-circle-notch fa-spin':useShoppingStore().currentlyUpdating, 'fa-shopping-cart ': !useShoppingStore().currentlyUpdating}"></i> <span :class="{'fa-circle-notch fa-spin':useShoppingStore().currentlyUpdating, 'fa-shopping-cart ': !useShoppingStore().currentlyUpdating}"></i> <span
class="d-none d-md-block ms-1">{{ $t('Shopping_list') }} ({{ useShoppingStore().totalFoods }})</span></v-tab> class="d-none d-md-block ms-1">{{ $t('Shopping_list') }} ({{ useShoppingStore().stats.countUnchecked }})</span></v-tab>
<v-tab value="recipes"><i class="fas fa-book fa-fw"></i> <span class="d-none d-md-block ms-1">{{ <v-tab value="recipes"><i class="fas fa-book fa-fw"></i> <span class="d-none d-md-block ms-1">{{
$t('Recipes') $t('Recipes')
}} ({{ useShoppingStore().getAssociatedRecipes().length }})</span></v-tab> }} ({{ useShoppingStore().getAssociatedRecipes().length }})</span></v-tab>
@@ -25,13 +25,9 @@
<v-list density="compact"> <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="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-divider></v-divider>
<v-list-item> <v-list-item>
<v-select hide-details :items="groupingOptionsItems" v-model="useUserPreferenceStore().deviceSettings.shopping_selected_grouping" <v-select hide-details :items="groupingOptionsItems" v-model="useUserPreferenceStore().deviceSettings.shopping_selected_grouping" :label="$t('GroupBy')">
:label="$t('GroupBy')">
</v-select> </v-select>
</v-list-item> </v-list-item>
<v-list-item v-if="useUserPreferenceStore().deviceSettings.shopping_selected_grouping == ShoppingGroupingOptions.CATEGORY"> <v-list-item v-if="useUserPreferenceStore().deviceSettings.shopping_selected_grouping == ShoppingGroupingOptions.CATEGORY">
@@ -69,94 +65,29 @@
</v-list> </v-list>
</v-menu> </v-menu>
<!-- <v-btn height="100%" rounded="0" variant="plain">--> <v-btn height="100%" rounded="0" variant="plain">
<!-- <i class="fa-solid fa-download"></i>--> <i class="fa-solid fa-download"></i>
<!-- <shopping-export-dialog></shopping-export-dialog>--> <shopping-export-dialog></shopping-export-dialog>
<!-- </v-btn>--> </v-btn>
<!-- <v-btn height="100%" rounded="0" variant="plain" @click="useShoppingStore().undoChange()">--> <v-btn height="100%" rounded="0" variant="plain" @click="useShoppingStore().undoChange()">
<!-- <i class="fa-solid fa-arrow-rotate-left"></i>--> <i class="fa-solid fa-arrow-rotate-left"></i>
<!-- </v-btn>--> </v-btn>
</v-tabs> </v-tabs>
<shopping-export-dialog v-model="exportDialog" activator="model"></shopping-export-dialog>
<v-window v-model="currentTab"> <v-window v-model="currentTab">
<v-window-item value="shopping"> <v-window-item value="shopping">
<v-container> <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-row class="pa-0" dense>
<v-col class="pa-0"> <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"> <v-chip-group v-model="useUserPreferenceStore().deviceSettings.shopping_selected_supermarket" v-if="supermarkets.length > 0">
<span v-if="useUserPreferenceStore().deviceSettings.shopping_selected_supermarket != null"> <v-chip v-for="s in supermarkets" :value="s" :key="s.id" label density="compact" variant="outlined" color="primary">{{ s.name }}</v-chip>
{{ useUserPreferenceStore().deviceSettings.shopping_selected_supermarket.name }} </v-chip-group>
</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') }}
</v-list-item>
</v-list>
</v-menu>
</v-chip>
<v-chip label density="compact" class="ms-1" variant="outlined"
:color="(useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list.length == 0 ? '' : 'secondary')" :prepend-icon="TShoppingList.icon"
append-icon="fa-solid fa-caret-down">
<template v-if="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list.filter(sl => sl != -1).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" :close-on-content-click="false">
<v-list density="compact" v-model:selected="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list" select-strategy="leaf">
<v-list-item @click="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list = [] ">
{{ $t('All') }}
</v-list-item>
<v-list-item :value="-1" @click="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list = [-1];">
<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>
{{ $t('None') }}
</v-list-item>
<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') }}
</v-list-item>
</v-list>
</v-menu>
</v-chip>
</v-col> </v-col>
</v-row> </v-row>
<v-row class="mt-0"> <v-row>
<v-col> <v-col>
<v-alert v-if="useShoppingStore().hasFailedItems()" color="warning" class="mb-2"> <v-alert v-if="useShoppingStore().hasFailedItems()" color="warning" class="mb-2">
<template #prepend> <template #prepend>
@@ -176,9 +107,9 @@
<v-skeleton-loader type="list-item"></v-skeleton-loader> <v-skeleton-loader type="list-item"></v-skeleton-loader>
<v-skeleton-loader type="list-item"></v-skeleton-loader> <v-skeleton-loader type="list-item"></v-skeleton-loader>
</v-list> </v-list>
<v-list class="mt-3" density="compact" v-model:selected="selectedLines" select-strategy="leaf" v-else> <v-list class="mt-3" density="compact" v-else>
<template v-for="category in useShoppingStore().entriesByGroup" :key="category.name"> <template v-for="category in useShoppingStore().getEntriesByGroup" :key="category.name">
<template v-if="isShoppingCategoryVisible(category)">
<v-list-subheader v-if="category.name === useShoppingStore().UNDEFINED_CATEGORY"><i>{{ $t('NoCategory') }}</i></v-list-subheader> <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-list-subheader v-else>{{ category.name }}</v-list-subheader>
@@ -189,6 +120,7 @@
</template> </template>
</template> </template>
</template>
</v-list> </v-list>
<!-- TODO remove once append to body for model select is working properly --> <!-- TODO remove once append to body for model select is working properly -->
@@ -314,14 +246,14 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, onMounted, ref, shallowRef, toRef, watch} from "vue"; import {computed, onMounted, ref} from "vue";
import {useShoppingStore} from "@/stores/ShoppingStore"; import {useShoppingStore} from "@/stores/ShoppingStore";
import {ApiApi, Recipe, ResponseError, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Supermarket} from "@/openapi"; import {ApiApi, Recipe, ResponseError, ShoppingListEntry, ShoppingListRecipe, Supermarket} from "@/openapi";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore"; import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
import ShoppingLineItem from "@/components/display/ShoppingLineItem.vue"; import ShoppingLineItem from "@/components/display/ShoppingLineItem.vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import ModelSelect from "@/components/inputs/ModelSelect.vue"; import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {IShoppingListFood, ShoppingGroupingOptions} from "@/types/Shopping"; import {ShoppingGroupingOptions} from "@/types/Shopping";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import NumberScalerDialog from "@/components/inputs/NumberScalerDialog.vue"; import NumberScalerDialog from "@/components/inputs/NumberScalerDialog.vue";
import SupermarketEditor from "@/components/model_editors/SupermarketEditor.vue"; import SupermarketEditor from "@/components/model_editors/SupermarketEditor.vue";
@@ -333,18 +265,13 @@ import {onBeforeRouteLeave} from "vue-router";
import {isShoppingCategoryVisible} from "@/utils/logic_utils.ts"; import {isShoppingCategoryVisible} from "@/utils/logic_utils.ts";
import ShoppingExportDialog from "@/components/dialogs/ShoppingExportDialog.vue"; import ShoppingExportDialog from "@/components/dialogs/ShoppingExportDialog.vue";
import AddToShoppingDialog from "@/components/dialogs/AddToShoppingDialog.vue"; import AddToShoppingDialog from "@/components/dialogs/AddToShoppingDialog.vue";
import {TShoppingList, TSupermarket} from "@/types/Models.ts";
const {t} = useI18n() const {t} = useI18n()
const exportDialog = ref(false)
const currentTab = ref("shopping") const currentTab = ref("shopping")
const supermarkets = ref([] as Supermarket[]) const supermarkets = ref([] as Supermarket[])
const shoppingLists = ref([] as ShoppingList[])
const manualAddRecipe = ref<undefined | Recipe>(undefined) const manualAddRecipe = ref<undefined | Recipe>(undefined)
const selectedLines = shallowRef([] as IShoppingListFood[])
/** /**
* VSelect items for shopping list grouping options with localized names * VSelect items for shopping list grouping options with localized names
*/ */
@@ -356,10 +283,6 @@ const groupingOptionsItems = computed(() => {
return items return items
}) })
watch(() => useUserPreferenceStore().deviceSettings, () => {
useShoppingStore().updateEntriesStructure()
}, {deep: true})
onMounted(() => { onMounted(() => {
addEventListener("visibilitychange", (event) => { addEventListener("visibilitychange", (event) => {
useShoppingStore().autoSyncHasFocus = (document.visibilityState === 'visible') useShoppingStore().autoSyncHasFocus = (document.visibilityState === 'visible')
@@ -381,7 +304,6 @@ onMounted(() => {
} }
loadSupermarkets() loadSupermarkets()
loadShoppingLists()
}) })
/** /**
@@ -456,20 +378,6 @@ function loadSupermarkets() {
}) })
} }
/**
* load a list of supermarkets
*/
function loadShoppingLists() {
let api = new ApiApi()
api.apiShoppingListList().then(r => {
shoppingLists.value = r.results
// TODO either recursive or add a "favorite" attribute to supermarkets for them to display at all
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
})
}
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,27 +0,0 @@
<template>
<div v-if="props.shoppingLists">
<slot name="prepend"></slot>
<v-chip class="me-1 mb-1" :color="shoppingList.color" :size="props.size" :variant="props.variant" label v-for="shoppingList in props.shoppingLists">
{{ shoppingList.name }}
</v-chip>
<slot name="append"></slot>
</div>
</template>
<script setup lang="ts">
import {Keyword, KeywordLabel, ShoppingList} from "@/openapi";
import {computed, PropType} from "vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
const props = defineProps({
shoppingLists: Array as PropType<Array<ShoppingList> | undefined>,
size: {type: String, default: 'x-small'},
variant: {type: String as PropType<NonNullable<"tonal" | "flat" | "text" | "elevated" | "outlined" | "plain"> | undefined>, default: 'outlined'},
})
</script>

View File

@@ -30,7 +30,6 @@
<v-textarea :label="$t('Description')" v-model="editingObj.description"></v-textarea> <v-textarea :label="$t('Description')" v-model="editingObj.description"></v-textarea>
<!-- TODO fix card overflow invisible, overflow-visible class is not working --> <!-- 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('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-form>
</v-tabs-window-item> </v-tabs-window-item>

View File

@@ -1,68 +0,0 @@
<template>
<model-editor-base
:loading="loading"
:dialog="dialog"
@save="saveObject"
@delete="deleteObject"
@close="emit('close'); editingObjChanged = false"
:is-update="isUpdate()"
:is-changed="editingObjChanged"
:model-class="modelClass"
:object-name="editingObjName()"
:editing-object="editingObj">
<v-card-text>
<v-form :disabled="loading">
<v-text-field :label="$t('Name')" v-model="editingObj.name"></v-text-field>
<v-textarea :label="$t('Description')" v-model="editingObj.description" :rows="2" auto-grow></v-textarea>
<v-color-picker :label="$t('Color')" v-model="editingObj.color" mode="hex" :modes="['hex']" show-swatches
:swatches="[['#ddbf86'],['#b98766'],['#b55e4f'],['#82aa8b'],['#385f84']]"></v-color-picker>
</v-form>
</v-card-text>
</model-editor-base>
</template>
<script setup lang="ts">
import {onMounted, PropType, watch} from "vue";
import {ShoppingList, ShoppingListEntry} from "@/openapi";
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
const props = defineProps({
item: {type: {} as PropType<ShoppingList>, required: false, default: null},
itemId: {type: [Number, String], required: false, default: undefined},
itemDefaults: {type: {} as PropType<ShoppingList>, required: false, default: {} as ShoppingList},
dialog: {type: Boolean, default: false}
})
const emit = defineEmits(['create', 'save', 'delete', 'close', 'changedState'])
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading, editingObj, editingObjChanged, modelClass} = useModelEditorFunctions<ShoppingList>('ShoppingList', emit)
/**
* watch prop changes and re-initialize editor
* required to embed editor directly into pages and be able to change item from the outside
*/
watch([() => props.item, () => props.itemId], () => {
initializeEditor()
})
// object specific data (for selects/display)
onMounted(() => {
initializeEditor()
})
/**
* component specific state setup logic
*/
function initializeEditor(){
setupState(props.item, props.itemId, {itemDefaults: props.itemDefaults})
}
</script>
<style scoped>
</style>

View File

@@ -27,7 +27,6 @@
<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('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('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('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 <v-number-input
class="mt-2" class="mt-2"

View File

@@ -16,7 +16,7 @@ export function useNavigation() {
{component: VListItem, prependIcon: '$recipes', title: 'Home', to: {name: 'StartPage', params: {}}}, {component: VListItem, prependIcon: '$recipes', title: 'Home', to: {name: 'StartPage', params: {}}},
{component: VListItem, prependIcon: '$search', title: t('Search'), to: {name: 'SearchPage', params: {}}}, {component: VListItem, prependIcon: '$search', title: t('Search'), to: {name: 'SearchPage', params: {}}},
{component: VListItem, prependIcon: '$mealplan', title: t('Meal_Plan'), to: {name: 'MealPlanPage', params: {}}}, {component: VListItem, prependIcon: '$mealplan', title: t('Meal_Plan'), to: {name: 'MealPlanPage', params: {}}},
{component: VListItem, prependIcon: '$shopping', title: t('Shopping'), to: {name: 'ShoppingListPage', params: {}}}, {component: VListItem, prependIcon: '$shopping', title: t('Shopping_list'), to: {name: 'ShoppingListPage', params: {}}},
{component: VListItem, prependIcon: 'fas fa-globe', title: t('Import'), to: {name: 'RecipeImportPage', params: {}}}, {component: VListItem, prependIcon: 'fas fa-globe', title: t('Import'), to: {name: 'RecipeImportPage', params: {}}},
{component: VListItem, prependIcon: '$books', title: t('Books'), to: {name: 'BooksPage', params: {}}}, {component: VListItem, prependIcon: '$books', title: t('Books'), to: {name: 'BooksPage', params: {}}},
{component: VListItem, prependIcon: 'fa-solid fa-folder-tree', title: t('Database'), to: {name: 'DatabasePage', params: {}}}, {component: VListItem, prependIcon: 'fa-solid fa-folder-tree', title: t('Database'), to: {name: 'DatabasePage', params: {}}},

View File

@@ -23,7 +23,6 @@
"AiModelHelp": "", "AiModelHelp": "",
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"All": "",
"App": "", "App": "",
"Apply": "", "Apply": "",
"Are_You_Sure": "", "Are_You_Sure": "",

View File

@@ -23,7 +23,6 @@
"AiModelHelp": "", "AiModelHelp": "",
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"All": "",
"App": "Приложение", "App": "Приложение",
"Apply": "", "Apply": "",
"Are_You_Sure": "Сигурен ли си?", "Are_You_Sure": "Сигурен ли си?",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Alineació", "Alignment": "Alineació",
"All": "",
"Amount": "Quantitat", "Amount": "Quantitat",
"App": "Aplicació", "App": "Aplicació",
"Apply": "", "Apply": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Zarovnání", "Alignment": "Zarovnání",
"All": "",
"Amount": "Množství", "Amount": "Množství",
"App": "Aplikace", "App": "Aplikace",
"Apply": "", "Apply": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Justering", "Alignment": "Justering",
"All": "",
"Amount": "Mængde", "Amount": "Mængde",
"App": "App", "App": "App",
"Apply": "", "Apply": "",

View File

@@ -39,7 +39,6 @@
"AiProvider": "AI Anbieter", "AiProvider": "AI Anbieter",
"AiProviderHelp": "Je nach Präferenz können verschiedene AI Anbieter angelegt werden. Diese können auch Space übergreifend sein.", "AiProviderHelp": "Je nach Präferenz können verschiedene AI Anbieter angelegt werden. Diese können auch Space übergreifend sein.",
"Alignment": "Ausrichtung", "Alignment": "Ausrichtung",
"All": "Alle",
"AllRecipes": "Alle Rezepte", "AllRecipes": "Alle Rezepte",
"Amount": "Menge", "Amount": "Menge",
"App": "App", "App": "App",
@@ -152,7 +151,6 @@
"Decimals": "Nachkommastellen", "Decimals": "Nachkommastellen",
"Default": "Standard", "Default": "Standard",
"DefaultPage": "Standardseite", "DefaultPage": "Standardseite",
"DefaultShoppingListHelp": "Standard Liste wenn dieses Lebensmittel auf die Einkaufsliste gesetzt wird.",
"Default_Unit": "Standardeinheit", "Default_Unit": "Standardeinheit",
"DelayFor": "Um {hours} Stunden verschieben", "DelayFor": "Um {hours} Stunden verschieben",
"DelayUntil": "Verzögerung bis", "DelayUntil": "Verzögerung bis",
@@ -377,7 +375,6 @@
"NoUnit": "Keine Einheit", "NoUnit": "Keine Einheit",
"No_ID": "ID nicht gefunden und kann nicht gelöscht werden.", "No_ID": "ID nicht gefunden und kann nicht gelöscht werden.",
"No_Results": "Keine Ergebnisse", "No_Results": "Keine Ergebnisse",
"None": "Keine",
"NotFound": "Nicht gefunden", "NotFound": "Nicht gefunden",
"NotFoundHelp": "Die gesuchte Seite konnte nicht gefunden werden.", "NotFoundHelp": "Die gesuchte Seite konnte nicht gefunden werden.",
"NotInShopping": "{food} befindet sich nicht auf Ihrer Einkaufsliste.", "NotInShopping": "{food} befindet sich nicht auf Ihrer Einkaufsliste.",
@@ -506,17 +503,14 @@
"Share": "Teilen", "Share": "Teilen",
"ShopLater": "Später kaufen", "ShopLater": "Später kaufen",
"ShopNow": "Jetzt kaufen", "ShopNow": "Jetzt kaufen",
"Shopping": "Einkaufen",
"ShoppingBackgroundSyncWarning": "Schlechte Netzwerkverbindung, Warten auf Synchronisation ...", "ShoppingBackgroundSyncWarning": "Schlechte Netzwerkverbindung, Warten auf Synchronisation ...",
"ShoppingList": "Einkaufsliste",
"ShoppingListEntry": "Einkaufslisten Eintrag", "ShoppingListEntry": "Einkaufslisten Eintrag",
"ShoppingListEntryHelp": "Einträge auf der Einkaufsliste können manuell oder automatisch durch Rezepte und Essenspläne erstellt werden.", "ShoppingListEntryHelp": "Einträge auf der Einkaufsliste können manuell oder automatisch durch Rezepte und Essenspläne erstellt werden.",
"ShoppingListHelp": "Erlaubt es Einträge auf verschiedene Listen zu setzten. Beispielsweise für verschiedene Supermärkte, Angebote oder Ereignisse. ",
"ShoppingListRecipe": "Einkaufslisten Rezepte", "ShoppingListRecipe": "Einkaufslisten Rezepte",
"Shopping_Categories": "Einkaufskategorien", "Shopping_Categories": "Einkaufskategorien",
"Shopping_Category": "Einkaufskategorie", "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_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. 100 g Kartoffeln", "Shopping_input_placeholder": "z.B. Kartoffeln/ 100 Kartoffeln/ 100 g Kartoffeln",
"Shopping_list": "Einkaufsliste", "Shopping_list": "Einkaufsliste",
"ShowDelayed": "Zeige verschobene Elemente", "ShowDelayed": "Zeige verschobene Elemente",
"ShowIngredients": "Zutaten anzeigen", "ShowIngredients": "Zutaten anzeigen",
@@ -618,8 +612,6 @@
"Unrated": "Unbewertet", "Unrated": "Unbewertet",
"Up": "Hoch", "Up": "Hoch",
"Update": "Aktualisieren", "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", "Update_Existing_Data": "Vorhandene Daten aktualisieren",
"Updated": "Aktualisiert", "Updated": "Aktualisiert",
"UpgradeNow": "Jetzt Upgraden", "UpgradeNow": "Jetzt Upgraden",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Ευθυγράμμιση", "Alignment": "Ευθυγράμμιση",
"All": "",
"Amount": "Ποσότητα", "Amount": "Ποσότητα",
"App": "Εφαρμογή", "App": "Εφαρμογή",
"Apply": "", "Apply": "",

View File

@@ -37,7 +37,6 @@
"AiProvider": "AI Provider", "AiProvider": "AI Provider",
"AiProviderHelp": "You can configure multiple AI providers according to your preferences. They can even be configured to work across multiple spaces.", "AiProviderHelp": "You can configure multiple AI providers according to your preferences. They can even be configured to work across multiple spaces.",
"Alignment": "Alignment", "Alignment": "Alignment",
"All": "All",
"AllRecipes": "All Recipes", "AllRecipes": "All Recipes",
"Amount": "Amount", "Amount": "Amount",
"App": "App", "App": "App",
@@ -150,7 +149,6 @@
"Decimals": "Decimals", "Decimals": "Decimals",
"Default": "Default", "Default": "Default",
"DefaultPage": "Default Page", "DefaultPage": "Default Page",
"DefaultShoppingListHelp": "Default List when this Food is added to the Shoppinglist.",
"Default_Unit": "Default Unit", "Default_Unit": "Default Unit",
"DelayFor": "Delay for {hours} hours", "DelayFor": "Delay for {hours} hours",
"DelayUntil": "Delay Until", "DelayUntil": "Delay Until",
@@ -375,7 +373,6 @@
"NoUnit": "No Unit", "NoUnit": "No Unit",
"No_ID": "ID not found, cannot delete.", "No_ID": "ID not found, cannot delete.",
"No_Results": "No Results", "No_Results": "No Results",
"None": "None",
"NotFound": "Not found", "NotFound": "Not found",
"NotFoundHelp": "The page or object you are looking for could not be found.", "NotFoundHelp": "The page or object you are looking for could not be found.",
"NotInShopping": "{food} is not in your shopping list.", "NotInShopping": "{food} is not in your shopping list.",
@@ -504,17 +501,14 @@
"Share": "Share", "Share": "Share",
"ShopLater": "Shop later", "ShopLater": "Shop later",
"ShopNow": "Shop now", "ShopNow": "Shop now",
"Shopping": "Shopping",
"ShoppingBackgroundSyncWarning": "Bad network, waiting to sync ...", "ShoppingBackgroundSyncWarning": "Bad network, waiting to sync ...",
"ShoppingList": "Shoppinglist",
"ShoppingListEntry": "Shoppinglist Entry", "ShoppingListEntry": "Shoppinglist Entry",
"ShoppingListEntryHelp": "Shopping list entries can be created manually or trough recipes and meal plans.", "ShoppingListEntryHelp": "Shopping list entries can be created manually or trough recipes and meal plans.",
"ShoppingListHelp": "Allows you to put entries on different lists. Can be used for different supermarkets, special offers or events. ",
"ShoppingListRecipe": "Shoppinglist Recipe", "ShoppingListRecipe": "Shoppinglist Recipe",
"Shopping_Categories": "Shopping Categories", "Shopping_Categories": "Shopping Categories",
"Shopping_Category": "Shopping Category", "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_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. 100 g Potatoes", "Shopping_input_placeholder": "e.g. Potato/100 Potatoes/100 g Potatoes",
"Shopping_list": "Shopping List", "Shopping_list": "Shopping List",
"ShowDelayed": "Show delayed items", "ShowDelayed": "Show delayed items",
"ShowIngredients": "Show Ingredients", "ShowIngredients": "Show Ingredients",
@@ -616,8 +610,6 @@
"Unrated": "Unrated", "Unrated": "Unrated",
"Up": "Up", "Up": "Up",
"Update": "Update", "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", "Update_Existing_Data": "Update Existing Data",
"Updated": "Updated", "Updated": "Updated",
"UpgradeNow": "Upgrade now", "UpgradeNow": "Upgrade now",

View File

@@ -36,7 +36,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Alineación", "Alignment": "Alineación",
"All": "",
"AllRecipes": "Todas las recetas", "AllRecipes": "Todas las recetas",
"Amount": "Cantidad", "Amount": "Cantidad",
"App": "Aplicación", "App": "Aplicación",

View File

@@ -27,7 +27,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Tasaus", "Alignment": "Tasaus",
"All": "",
"Amount": "Määrä", "Amount": "Määrä",
"App": "Applikaatio", "App": "Applikaatio",
"Apply": "", "Apply": "",

View File

@@ -37,7 +37,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Alignement", "Alignment": "Alignement",
"All": "",
"AllRecipes": "Toutes les recettes", "AllRecipes": "Toutes les recettes",
"Amount": "Quantité", "Amount": "Quantité",
"App": "Appli", "App": "Appli",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "יישור", "Alignment": "יישור",
"All": "",
"Amount": "כמות", "Amount": "כמות",
"App": "אפליקציה", "App": "אפליקציה",
"Apply": "", "Apply": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Poravnanje", "Alignment": "Poravnanje",
"All": "",
"Amount": "Količina", "Amount": "Količina",
"App": "Aplikacija", "App": "Aplikacija",
"Apply": "", "Apply": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Igazítás", "Alignment": "Igazítás",
"All": "",
"Amount": "Összeg", "Amount": "Összeg",
"App": "Applikáció", "App": "Applikáció",
"Apply": "", "Apply": "",

View File

@@ -17,7 +17,6 @@
"AiModelHelp": "", "AiModelHelp": "",
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"All": "",
"Apply": "", "Apply": "",
"Automate": "Ավտոմատացնել", "Automate": "Ավտոմատացնել",
"BatchDeleteConfirm": "", "BatchDeleteConfirm": "",

View File

@@ -25,7 +25,6 @@
"AiModelHelp": "", "AiModelHelp": "",
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"All": "",
"App": "", "App": "",
"Apply": "", "Apply": "",
"Are_You_Sure": "", "Are_You_Sure": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "", "Alignment": "",
"All": "",
"Amount": "", "Amount": "",
"App": "", "App": "",
"Apply": "", "Apply": "",

View File

@@ -38,7 +38,6 @@
"AiProvider": "Fornitore AI", "AiProvider": "Fornitore AI",
"AiProviderHelp": "Puoi configurare più fornitori AI in base alle tue preferenze. Possono essere configurati anche per lavorare tra più spazi.", "AiProviderHelp": "Puoi configurare più fornitori AI in base alle tue preferenze. Possono essere configurati anche per lavorare tra più spazi.",
"Alignment": "Allineamento", "Alignment": "Allineamento",
"All": "",
"AllRecipes": "Tutte le ricette", "AllRecipes": "Tutte le ricette",
"Amount": "Quantità", "Amount": "Quantità",
"App": "Applicazione", "App": "Applicazione",
@@ -454,7 +453,7 @@
"RecipeBookHelp": "I ricettari contengono voci di ricette oppure possono essere compilati automaticamente utilizzando filtri di ricerca salvati. ", "RecipeBookHelp": "I ricettari contengono voci di ricette oppure possono essere compilati automaticamente utilizzando filtri di ricerca salvati. ",
"RecipeHelp": "Le ricette sono la base del Tandoor e sono composte da informazioni generali e passaggi, oltre che da ingredienti, istruzioni e altro ancora. ", "RecipeHelp": "Le ricette sono la base del Tandoor e sono composte da informazioni generali e passaggi, oltre che da ingredienti, istruzioni e altro ancora. ",
"RecipeStepsHelp": "Ingredienti, istruzioni e altro possono essere modificati nella scheda Step.", "RecipeStepsHelp": "Ingredienti, istruzioni e altro possono essere modificati nella scheda Step.",
"RecipeStructure": "Struttura ricetta", "RecipeStructure": "",
"Recipe_Book": "Libro di ricette", "Recipe_Book": "Libro di ricette",
"Recipe_Image": "Immagine ricetta", "Recipe_Image": "Immagine ricetta",
"Recipes": "Ricette", "Recipes": "Ricette",
@@ -542,7 +541,7 @@
"Space_Cosmetic_Settings": "Alcune impostazioni cosmetiche possono essere modificate dagli amministratori dell'istanza e sovrascriveranno le impostazioni client per quell'istanza.", "Space_Cosmetic_Settings": "Alcune impostazioni cosmetiche possono essere modificate dagli amministratori dell'istanza e sovrascriveranno le impostazioni client per quell'istanza.",
"Split": "Dividi", "Split": "Dividi",
"Split_All_Steps": "Divide tutte le righe in step separati.", "Split_All_Steps": "Divide tutte le righe in step separati.",
"Start": "Avvia", "Start": "",
"StartDate": "Data d'inizio", "StartDate": "Data d'inizio",
"Starting_Day": "Giorno di inizio della settimana", "Starting_Day": "Giorno di inizio della settimana",
"StartsWith": "Inizia con", "StartsWith": "Inizia con",

View File

@@ -36,7 +36,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "", "Alignment": "",
"All": "",
"AllRecipes": "", "AllRecipes": "",
"Amount": "", "Amount": "",
"App": "", "App": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "", "Alignment": "",
"All": "",
"Amount": "Suma", "Amount": "Suma",
"App": "", "App": "",
"Apply": "", "Apply": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "", "Alignment": "",
"All": "",
"Amount": "", "Amount": "",
"App": "", "App": "",
"Apply": "", "Apply": "",

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Justering", "Alignment": "Justering",
"All": "",
"Amount": "Mengde", "Amount": "Mengde",
"App": "App", "App": "App",
"Apply": "", "Apply": "",

View File

@@ -56,7 +56,7 @@
"BaseUnit": "Basiseenheid", "BaseUnit": "Basiseenheid",
"BaseUnitHelp": "Standaardeenheid om automatische eenheden om te rekenen", "BaseUnitHelp": "Standaardeenheid om automatische eenheden om te rekenen",
"Basics": "Basisprincipes", "Basics": "Basisprincipes",
"BatchDeleteConfirm": "Wil je alle getoonde items verwijderen? Dit kan niet ongedaan worden gemaakt! WAARSCHUWING: Het is mogelijk dat hierdoor objecten worden verwijderd die ook elders worden gebruikt. ", "BatchDeleteConfirm": "Wil je alle getoonde items verwijderen? Dit kan niet ongedaan worden gemaakt!",
"BatchDeleteHelp": "Als een item niet verwijderd kan worden, wordt het ergens gebruikt. ", "BatchDeleteHelp": "Als een item niet verwijderd kan worden, wordt het ergens gebruikt. ",
"BatchEdit": "Batchbewerking", "BatchEdit": "Batchbewerking",
"BatchEditUpdatingItemsCount": "{count} {type} bewerken", "BatchEditUpdatingItemsCount": "{count} {type} bewerken",
@@ -450,7 +450,7 @@
"RecipeBookHelp": "Receptboeken bevatten receptenboekitems of kunnen automatisch gevuld worden met behulp van opgeslagen zoekfilters. ", "RecipeBookHelp": "Receptboeken bevatten receptenboekitems of kunnen automatisch gevuld worden met behulp van opgeslagen zoekfilters. ",
"RecipeHelp": "Recepten vormen de basis van Tandoor en bestaan uit algemene informatie en stappen, opgebouwd uit ingrediënten, instructies en meer. ", "RecipeHelp": "Recepten vormen de basis van Tandoor en bestaan uit algemene informatie en stappen, opgebouwd uit ingrediënten, instructies en meer. ",
"RecipeStepsHelp": "Ingrediënten, instructies en meer kun je bewerken in het tabblad stappen.", "RecipeStepsHelp": "Ingrediënten, instructies en meer kun je bewerken in het tabblad stappen.",
"RecipeStructure": "Receptstructuur", "RecipeStructure": "",
"Recipe_Book": "Kookboek", "Recipe_Book": "Kookboek",
"Recipe_Image": "Receptafbeelding", "Recipe_Image": "Receptafbeelding",
"Recipes": "Recepten", "Recipes": "Recepten",
@@ -537,7 +537,7 @@
"Space_Cosmetic_Settings": "Sommige weergave-instellingen kunnen worden geforceerd door de administrator van de ruimte en zullen de persoonlijke instellingen voor die ruimte overschrijven.", "Space_Cosmetic_Settings": "Sommige weergave-instellingen kunnen worden geforceerd door de administrator van de ruimte en zullen de persoonlijke instellingen voor die ruimte overschrijven.",
"Split": "Splitsen", "Split": "Splitsen",
"Split_All_Steps": "Splits alle rijen in aparte stappen.", "Split_All_Steps": "Splits alle rijen in aparte stappen.",
"Start": "Start", "Start": "",
"StartDate": "Startdatum", "StartDate": "Startdatum",
"Starting_Day": "Eerste dag van de week", "Starting_Day": "Eerste dag van de week",
"StartsWith": "Begint met", "StartsWith": "Begint met",
@@ -869,13 +869,5 @@
"view_recipe": "Bekijk recept", "view_recipe": "Bekijk recept",
"warning_duplicate_filter": "Waarschuwing: door technische beperkingen kan het hebben van meerdere filters of dezelfde combinatie (en/of/niet) tot onverwachte resultaten leiden.", "warning_duplicate_filter": "Waarschuwing: door technische beperkingen kan het hebben van meerdere filters of dezelfde combinatie (en/of/niet) tot onverwachte resultaten leiden.",
"warning_feature_beta": "Deze functie zit op dit moment in de BETA (test) fase. Verwacht hier bugs en toekomstige wijzigingen die tot het verlies van data kunnen leiden bij het gebruik.", "warning_feature_beta": "Deze functie zit op dit moment in de BETA (test) fase. Verwacht hier bugs en toekomstige wijzigingen die tot het verlies van data kunnen leiden bij het gebruik.",
"warning_space_delete": "Je kunt jouw ruimte verwijderen inclusief alle recepten, boodschappenlijstjes, maaltijdplannen en alles wat je verder aangemaakt hebt. Dit kan niet ongedaan worden gemaakt! Weet je het zeker?", "warning_space_delete": "Je kunt jouw ruimte verwijderen inclusief alle recepten, boodschappenlijstjes, maaltijdplannen en alles wat je verder aangemaakt hebt. Dit kan niet ongedaan worden gemaakt! Weet je het zeker?"
"AboutTandoor": "Tandoor is een open source platform om recepten, maaltijdplannen, boodschappenlijstjes en meer te beheren.",
"ImportIntoTandoorHelp": "Om dit recept in je eigen Tandoor-collectie te importeren, volg je de volgende stappen.",
"SelfHosted": "Zelfgehost",
"CreateAccount": "Maak account",
"Shopping": "Boodschappen",
"ShoppingList": "Boodschappenlijst",
"ShoppingListHelp": "Hiermee kun je items op verschillende lijsten plaatsen. Dit kan worden gebruikt voor verschillende supermarkten, speciale aanbiedingen of evenementen. ",
"Finish": "Eind"
} }

View File

@@ -35,7 +35,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Wyrównanie", "Alignment": "Wyrównanie",
"All": "",
"AllRecipes": "Wszystkie przepisy", "AllRecipes": "Wszystkie przepisy",
"Amount": "Ilość", "Amount": "Ilość",
"App": "Aplikacja", "App": "Aplikacja",

View File

@@ -19,7 +19,6 @@
"Added_on": "Adicionado a", "Added_on": "Adicionado a",
"Advanced": "Avançado", "Advanced": "Avançado",
"Alignment": "Alinhamento", "Alignment": "Alinhamento",
"All": "",
"Amount": "Quantidade", "Amount": "Quantidade",
"Apply": "", "Apply": "",
"Auto_Planner": "", "Auto_Planner": "",

View File

@@ -36,7 +36,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Alinhamento", "Alignment": "Alinhamento",
"All": "",
"AllRecipes": "Todas Receitas", "AllRecipes": "Todas Receitas",
"Amount": "Quantidade", "Amount": "Quantidade",
"App": "Aplicação", "App": "Aplicação",

View File

@@ -26,7 +26,6 @@
"AiModelHelp": "", "AiModelHelp": "",
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"All": "",
"Amount": "Cantitate", "Amount": "Cantitate",
"App": "Aplicație", "App": "Aplicație",
"Apply": "", "Apply": "",

View File

@@ -37,7 +37,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Выравнивание", "Alignment": "Выравнивание",
"All": "",
"AllRecipes": "Все рецепты", "AllRecipes": "Все рецепты",
"Amount": "Количество", "Amount": "Количество",
"App": "Приложение", "App": "Приложение",

View File

@@ -453,7 +453,7 @@
"RecipeBookHelp": "Knjige receptov vsebujejo vnose v knjige receptov ali pa se lahko samodejno izpolnijo z uporabo shranjenih iskalnih filtrov. ", "RecipeBookHelp": "Knjige receptov vsebujejo vnose v knjige receptov ali pa se lahko samodejno izpolnijo z uporabo shranjenih iskalnih filtrov. ",
"RecipeHelp": "Recepti so osnova Tandoorja in so sestavljeni iz splošnih informacij in korakov, sestavljenih iz sestavin, navodil in še več. ", "RecipeHelp": "Recepti so osnova Tandoorja in so sestavljeni iz splošnih informacij in korakov, sestavljenih iz sestavin, navodil in še več. ",
"RecipeStepsHelp": "Sestavine, navodila in drugo lahko urejate v zavihku Koraki.", "RecipeStepsHelp": "Sestavine, navodila in drugo lahko urejate v zavihku Koraki.",
"RecipeStructure": "Struktura recepta", "RecipeStructure": "",
"Recipe_Book": "Knjiga receptov", "Recipe_Book": "Knjiga receptov",
"Recipe_Image": "Slika recepta", "Recipe_Image": "Slika recepta",
"Recipes": "Recepti", "Recipes": "Recepti",
@@ -541,7 +541,7 @@
"Space_Cosmetic_Settings": "Nekatere kozmetične nastavitve lahko spremenijo skrbniki prostora in bodo preglasile nastavitve odjemalca za ta prostor.", "Space_Cosmetic_Settings": "Nekatere kozmetične nastavitve lahko spremenijo skrbniki prostora in bodo preglasile nastavitve odjemalca za ta prostor.",
"Split": "Razdelitev", "Split": "Razdelitev",
"Split_All_Steps": "Vse vrstice razdelite na ločene korake.", "Split_All_Steps": "Vse vrstice razdelite na ločene korake.",
"Start": "Začni", "Start": "",
"StartDate": "Začetni datum", "StartDate": "Začetni datum",
"Starting_Day": "Začetni dan v tednu", "Starting_Day": "Začetni dan v tednu",
"StartsWith": "Začne se s/z", "StartsWith": "Začne se s/z",
@@ -872,8 +872,5 @@
"view_recipe": "Oglejte si recept", "view_recipe": "Oglejte si recept",
"warning_duplicate_filter": "Opozorilo: Zaradi tehničnih omejitev lahko uporaba več filtrov iste kombinacije (in/ali/ne) prinese nepričakovane rezultate.", "warning_duplicate_filter": "Opozorilo: Zaradi tehničnih omejitev lahko uporaba več filtrov iste kombinacije (in/ali/ne) prinese nepričakovane rezultate.",
"warning_feature_beta": "Ta funkcija je trenutno v stanju BETA (testiranje). Pri uporabi te funkcije pričakujte napake in morebitne prelomne spremembe v prihodnosti (morda izgubite podatke, povezane s to funkcijo).", "warning_feature_beta": "Ta funkcija je trenutno v stanju BETA (testiranje). Pri uporabi te funkcije pričakujte napake in morebitne prelomne spremembe v prihodnosti (morda izgubite podatke, povezane s to funkcijo).",
"warning_space_delete": "Izbrišete lahko svoj prostor, vključno z vsemi recepti, nakupovalnimi seznami, načrti obrokov in vsem drugim, kar ste ustvarili. Tega ni mogoče preklicati! Ste prepričani, da želite to storiti?", "warning_space_delete": "Izbrišete lahko svoj prostor, vključno z vsemi recepti, nakupovalnimi seznami, načrti obrokov in vsem drugim, kar ste ustvarili. Tega ni mogoče preklicati! Ste prepričani, da želite to storiti?"
"Shopping": "Nakupovanje",
"ShoppingList": "Nakupovalni seznam",
"ShoppingListHelp": "Omogoča dodajanje vnosov na različne sezname. Uporablja se lahko za različne supermarkete, posebne ponudbe ali dogodke. "
} }

View File

@@ -36,7 +36,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Orientering", "Alignment": "Orientering",
"All": "",
"AllRecipes": "Alla recept", "AllRecipes": "Alla recept",
"Amount": "Mängd", "Amount": "Mängd",
"App": "App", "App": "App",
@@ -151,13 +150,11 @@
"DelayFor": "Fördröjning på {hours} timmar", "DelayFor": "Fördröjning på {hours} timmar",
"DelayUntil": "Fördröjning till", "DelayUntil": "Fördröjning till",
"Delete": "Radera", "Delete": "Radera",
"DeleteConfirmQuestion": "Är du säker på att du vill ta bort detta objekt?",
"DeleteShoppingConfirm": "Är du säker på att du vill ta bort all {food} från inköpslistan?", "DeleteShoppingConfirm": "Är du säker på att du vill ta bort all {food} från inköpslistan?",
"DeleteSomething": "", "DeleteSomething": "",
"Delete_All": "Radera alla", "Delete_All": "Radera alla",
"Delete_Food": "Ta bort livsmedel", "Delete_Food": "Ta bort livsmedel",
"Delete_Keyword": "Ta bort nyckelord", "Delete_Keyword": "Ta bort nyckelord",
"Deleted": "Borttagen",
"Description": "Beskrivning", "Description": "Beskrivning",
"Description_Replace": "Ersätt beskrivning", "Description_Replace": "Ersätt beskrivning",
"Disable": "Inaktivera", "Disable": "Inaktivera",
@@ -698,5 +695,7 @@
"view_recipe": "Visa recept", "view_recipe": "Visa recept",
"warning_duplicate_filter": "Varning: På grund av tekniska begränsningar kan flera filter av samma kombination (och/eller/inte) ge oväntade resultat.", "warning_duplicate_filter": "Varning: På grund av tekniska begränsningar kan flera filter av samma kombination (och/eller/inte) ge oväntade resultat.",
"warning_feature_beta": "Den här funktionen är för närvarande i ett BETA-läge (testning). Förvänta dig buggar och eventuellt större ändringar i framtiden (möjligtvis framtida data kan gå förlorad) när du använder den här funktionen.", "warning_feature_beta": "Den här funktionen är för närvarande i ett BETA-läge (testning). Förvänta dig buggar och eventuellt större ändringar i framtiden (möjligtvis framtida data kan gå förlorad) när du använder den här funktionen.",
"warning_space_delete": "Du kan ta bort ditt utrymme inklusive alla recept, inköpslistor, måltidsplaner och allt annat du har skapat. Detta kan inte ångras! Är du säker på att du vill göra detta?" "warning_space_delete": "Du kan ta bort ditt utrymme inklusive alla recept, inköpslistor, måltidsplaner och allt annat du har skapat. Detta kan inte ångras! Är du säker på att du vill göra detta?",
"Deleted": "Borttagen",
"DeleteConfirmQuestion": "Är du säker på att du vill ta bort detta objekt?"
} }

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "Hizalama", "Alignment": "Hizalama",
"All": "",
"Amount": "Miktar", "Amount": "Miktar",
"App": "Uygulama", "App": "Uygulama",
"Apply": "", "Apply": "",

View File

@@ -6,7 +6,7 @@
"APIKey": "ключ API", "APIKey": "ключ API",
"API_Browser": "браузер API", "API_Browser": "браузер API",
"API_Documentation": "Документація API", "API_Documentation": "Документація API",
"AboutTandoor": "Tandoor — це платформа з відкритим кодом для управління рецептами, меню, списками покупок тощо.", "AboutTandoor": "Tandoor — це платформа з відкритим кодом для управління рецептами, планами харчування, списками покупок тощо.",
"AccessTokenHelp": "Ключі доступу для REST API.", "AccessTokenHelp": "Ключі доступу для REST API.",
"Access_Token": "Токен доступу", "Access_Token": "Токен доступу",
"Account": "Обліковий запис", "Account": "Обліковий запис",
@@ -119,7 +119,7 @@
"CreateAccount": "Створити акаунт", "CreateAccount": "Створити акаунт",
"CreateFirstRecipe": "Створіть свій перший рецепт за допомогою редактора рецептів.", "CreateFirstRecipe": "Створіть свій перший рецепт за допомогою редактора рецептів.",
"CreateInvitation": "Створити запрошення", "CreateInvitation": "Створити запрошення",
"Create_Meal_Plan_Entry": "Створити запис в меню", "Create_Meal_Plan_Entry": "Створити запис в плані харчування",
"Create_New_Food": "Додати Нову Їжу", "Create_New_Food": "Додати Нову Їжу",
"Create_New_Keyword": "Додати Нове Ключове слово", "Create_New_Keyword": "Додати Нове Ключове слово",
"Create_New_Meal_Type": "Додати Новий Тип Страви", "Create_New_Meal_Type": "Додати Новий Тип Страви",
@@ -141,7 +141,7 @@
"DELETE_ERROR": "Помилка під час видалення", "DELETE_ERROR": "Помилка під час видалення",
"Data_Import_Info": "Покращуйте свій Простір, імпортуючи курований спільнотою список продуктів, одиниць виміру тощо, щоб вдосконалити свою колекцію рецептів.", "Data_Import_Info": "Покращуйте свій Простір, імпортуючи курований спільнотою список продуктів, одиниць виміру тощо, щоб вдосконалити свою колекцію рецептів.",
"Database": "База даних", "Database": "База даних",
"DatabaseHelp": "Tandoor використовує багато різних речей, щоб ви могли створювати рецепти, списки покупок, меню тощо. Тут ви можете керувати всіма цими моделями.", "DatabaseHelp": "Tandoor використовує багато різних речей, щоб ви могли створювати рецепти, списки покупок, плани харчування тощо. Тут ви можете керувати всіма цими моделями.",
"Datatype": "Тип данних", "Datatype": "Тип данних",
"Date": "Дата", "Date": "Дата",
"Day": "День", "Day": "День",
@@ -178,7 +178,7 @@
"Edit": "Редагувати", "Edit": "Редагувати",
"Edit_Food": "Редагувати Їжу", "Edit_Food": "Редагувати Їжу",
"Edit_Keyword": "Редагувати Ключове слово", "Edit_Keyword": "Редагувати Ключове слово",
"Edit_Meal_Plan_Entry": "Редагувати запис в меню", "Edit_Meal_Plan_Entry": "Редагувати запис в плані харчування",
"Edit_Recipe": "Редагувати Рецепт", "Edit_Recipe": "Редагувати Рецепт",
"Email": "Електронна пошта", "Email": "Електронна пошта",
"Empty": "Пусто", "Empty": "Пусто",
@@ -315,11 +315,11 @@
"ManageSubscription": "Керувати підпискою", "ManageSubscription": "Керувати підпискою",
"Manage_Books": "Управління Книжкою", "Manage_Books": "Управління Книжкою",
"Manage_Emails": "Керувати поштовими адресами", "Manage_Emails": "Керувати поштовими адресами",
"MealPlanHelp": "Меню — це запис у календарі, який використовується для планування меню. Він повинен містити рецепт або назву і може бути пов'язаний із списками покупок. ", "MealPlanHelp": "Меню — це запис у календарі, який використовується для планування харчування. Він повинен містити рецепт або назву і може бути пов'язаний із списками покупок. ",
"MealPlanShoppingHelp": "Записи у вашому списку покупок можуть бути пов'язані з меню, щоб сортувати список або оновлювати/видаляти їх всі одразу. При створенні меню з рецептом записи у списку покупок для цього рецепту можуть створюватися автоматично (налаштування). ", "MealPlanShoppingHelp": "Записи у вашому списку покупок можуть бути пов'язані з меню, щоб сортувати список або оновлювати/видаляти їх всі одразу. При створенні меню з рецептом записи у списку покупок для цього рецепту можуть створюватися автоматично (налаштування). ",
"MealTypeHelp": "Типи меню дозволяють сортувати ваші меню ", "MealTypeHelp": "Типи меню дозволяють сортувати ваші меню ",
"Meal_Plan": "Меню", "Meal_Plan": "План Харчування",
"Meal_Plan_Days": "Майбутні меню", "Meal_Plan_Days": "Майбутній план харчування",
"Meal_Type": "Тип страви", "Meal_Type": "Тип страви",
"Meal_Type_Required": "Тип страви є обов'язковим", "Meal_Type_Required": "Тип страви є обов'язковим",
"Meal_Types": "Типи страви", "Meal_Types": "Типи страви",
@@ -452,7 +452,7 @@
"RecipeBookHelp": "Рецептурники містять записи з книги рецептів або можуть бути автоматично заповнені за допомогою збережених фільтрів пошуку. ", "RecipeBookHelp": "Рецептурники містять записи з книги рецептів або можуть бути автоматично заповнені за допомогою збережених фільтрів пошуку. ",
"RecipeHelp": "Рецепти є основою Tandoor і складаються із загальної інформації та покрокових інструкцій, що містять перелік інгредієнтів, інструкції тощо. ", "RecipeHelp": "Рецепти є основою Tandoor і складаються із загальної інформації та покрокових інструкцій, що містять перелік інгредієнтів, інструкції тощо. ",
"RecipeStepsHelp": "Інгредієнти, інструкції та інше можна редагувати у вкладці «Кроки».", "RecipeStepsHelp": "Інгредієнти, інструкції та інше можна редагувати у вкладці «Кроки».",
"RecipeStructure": "Структура рецепта", "RecipeStructure": "",
"Recipe_Book": "Книга Рецептів", "Recipe_Book": "Книга Рецептів",
"Recipe_Image": "Зображення Рецепту", "Recipe_Image": "Зображення Рецепту",
"Recipes": "Рецепти", "Recipes": "Рецепти",
@@ -507,7 +507,7 @@
"ShoppingListRecipe": "Рецепт у списку покупок", "ShoppingListRecipe": "Рецепт у списку покупок",
"Shopping_Categories": "Категорії Покупок", "Shopping_Categories": "Категорії Покупок",
"Shopping_Category": "Категорія Покупок", "Shopping_Category": "Категорія Покупок",
"Shopping_List_Empty": "Ваш список покупок зараз пустий, ви можете додати товари за допомогою контекстного меню для записів зі планування меню (права кнопка мишки на картку або на ліву кнопку на іконку меню)", "Shopping_List_Empty": "Ваш список покупок зараз пустий, ви можете додати товари за допомогою контекстного меню плану харчування (права кнопка мишки на картку або на ліву кнопку на іконку меню)",
"Shopping_input_placeholder": "напр. Картопля/100 Картопли/100гр. Картоплі", "Shopping_input_placeholder": "напр. Картопля/100 Картопли/100гр. Картоплі",
"Shopping_list": "Список Покупок", "Shopping_list": "Список Покупок",
"ShowDelayed": "Показати Відкладені Предмети", "ShowDelayed": "Показати Відкладені Предмети",
@@ -540,7 +540,7 @@
"Space_Cosmetic_Settings": "Деякі косметичні налаштування можуть бути змінені адміністраторами простору і замінять налаштування клієнта для цього простору.", "Space_Cosmetic_Settings": "Деякі косметичні налаштування можуть бути змінені адміністраторами простору і замінять налаштування клієнта для цього простору.",
"Split": "Розділити", "Split": "Розділити",
"Split_All_Steps": "Розділити всі рядки на окремі кроки.", "Split_All_Steps": "Розділити всі рядки на окремі кроки.",
"Start": "Старт", "Start": "",
"StartDate": "Початкова дата", "StartDate": "Початкова дата",
"Starting_Day": "Початковий день тижня", "Starting_Day": "Початковий день тижня",
"StartsWith": "Починається з", "StartsWith": "Починається з",
@@ -748,7 +748,7 @@
"make_now": "Зробити зараз", "make_now": "Зробити зараз",
"make_now_count": "Найбільш відсутні інгредієнти", "make_now_count": "Найбільш відсутні інгредієнти",
"mark_complete": "Позначити як завершене", "mark_complete": "Позначити як завершене",
"mealplan_autoadd_shopping": "Автоматично Додати Меню", "mealplan_autoadd_shopping": "Автоматично Додати План Харчування",
"mealplan_autoadd_shopping_desc": "Автоматично додавати інгредієнти з меню до списку покупок.", "mealplan_autoadd_shopping_desc": "Автоматично додавати інгредієнти з меню до списку покупок.",
"mealplan_autoexclude_onhand": "Виключити наявні продукти", "mealplan_autoexclude_onhand": "Виключити наявні продукти",
"mealplan_autoexclude_onhand_desc": "Додаючи меню до списку покупок (вручну або автоматично), виключіть інгредієнти, які вже є в наявності.", "mealplan_autoexclude_onhand_desc": "Додаючи меню до списку покупок (вручну або автоматично), виключіть інгредієнти, які вже є в наявності.",
@@ -869,8 +869,5 @@
"view_recipe": "Подивитись рецепт", "view_recipe": "Подивитись рецепт",
"warning_duplicate_filter": "Попередження: через технічні обмеження використання декількох фільтрів з однаковою комбінацією (та/або/не) може призвести до несподіваних результатів.", "warning_duplicate_filter": "Попередження: через технічні обмеження використання декількох фільтрів з однаковою комбінацією (та/або/не) може призвести до несподіваних результатів.",
"warning_feature_beta": "Наразі ця функціональність знаходиться в стані BETA (тестування). Будь ласка, будьте готові до помилок і можливих змін у майбутньому (можливо, втрата даних, пов'язаних з функціональністю) під час використання.", "warning_feature_beta": "Наразі ця функціональність знаходиться в стані BETA (тестування). Будь ласка, будьте готові до помилок і можливих змін у майбутньому (можливо, втрата даних, пов'язаних з функціональністю) під час використання.",
"warning_space_delete": "Ви можете видалити ваш простір разом зі всіма рецептами, списками покупок, меню і всім іншим, що ви створили. Ця дія незворотня! Ви впевнені, що бажаєте це зробити?", "warning_space_delete": "Ви можете видалити ваш простір разом зі всіма рецептами, списками покупок, планами харчування і всім іншим, що ви створили. Ця дія незворотня! Ви впевнені, що бажаєте це зробити?"
"Shopping": "Покупки",
"ShoppingList": "Лист покупок",
"ShoppingListHelp": "Дозволяє розміщувати записи в різних списках. Може використовуватися для різних супермаркетів, спеціальних пропозицій або подій. "
} }

View File

@@ -26,7 +26,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "校准", "Alignment": "校准",
"All": "",
"Amount": "数量", "Amount": "数量",
"App": "应用", "App": "应用",
"Apply": "", "Apply": "",

View File

@@ -36,7 +36,6 @@
"AiProvider": "", "AiProvider": "",
"AiProviderHelp": "", "AiProviderHelp": "",
"Alignment": "對齊", "Alignment": "對齊",
"All": "",
"AllRecipes": "所有食譜", "AllRecipes": "所有食譜",
"Amount": "數量", "Amount": "數量",
"App": "應用程式", "App": "應用程式",

View File

@@ -31,7 +31,6 @@ models/FdcQueryFoods.ts
models/Food.ts models/Food.ts
models/FoodBatchUpdate.ts models/FoodBatchUpdate.ts
models/FoodInheritField.ts models/FoodInheritField.ts
models/FoodShopping.ts
models/FoodShoppingUpdate.ts models/FoodShoppingUpdate.ts
models/FoodSimple.ts models/FoodSimple.ts
models/GenericModelReference.ts models/GenericModelReference.ts
@@ -95,7 +94,6 @@ models/PaginatedRecipeBookList.ts
models/PaginatedRecipeImportList.ts models/PaginatedRecipeImportList.ts
models/PaginatedRecipeOverviewList.ts models/PaginatedRecipeOverviewList.ts
models/PaginatedShoppingListEntryList.ts models/PaginatedShoppingListEntryList.ts
models/PaginatedShoppingListList.ts
models/PaginatedShoppingListRecipeList.ts models/PaginatedShoppingListRecipeList.ts
models/PaginatedSpaceList.ts models/PaginatedSpaceList.ts
models/PaginatedStepList.ts models/PaginatedStepList.ts
@@ -142,7 +140,6 @@ models/PatchedRecipeBook.ts
models/PatchedRecipeBookEntry.ts models/PatchedRecipeBookEntry.ts
models/PatchedRecipeImport.ts models/PatchedRecipeImport.ts
models/PatchedSearchPreference.ts models/PatchedSearchPreference.ts
models/PatchedShoppingList.ts
models/PatchedShoppingListEntry.ts models/PatchedShoppingListEntry.ts
models/PatchedShoppingListRecipe.ts models/PatchedShoppingListRecipe.ts
models/PatchedSpace.ts models/PatchedSpace.ts
@@ -177,7 +174,6 @@ models/SearchFields.ts
models/SearchPreference.ts models/SearchPreference.ts
models/ServerSettings.ts models/ServerSettings.ts
models/ShareLink.ts models/ShareLink.ts
models/ShoppingList.ts
models/ShoppingListEntry.ts models/ShoppingListEntry.ts
models/ShoppingListEntryBulk.ts models/ShoppingListEntryBulk.ts
models/ShoppingListEntryBulkCreate.ts models/ShoppingListEntryBulkCreate.ts

View File

@@ -85,7 +85,6 @@ import type {
PaginatedRecipeImportList, PaginatedRecipeImportList,
PaginatedRecipeOverviewList, PaginatedRecipeOverviewList,
PaginatedShoppingListEntryList, PaginatedShoppingListEntryList,
PaginatedShoppingListList,
PaginatedShoppingListRecipeList, PaginatedShoppingListRecipeList,
PaginatedSpaceList, PaginatedSpaceList,
PaginatedStepList, PaginatedStepList,
@@ -132,7 +131,6 @@ import type {
PatchedRecipeBookEntry, PatchedRecipeBookEntry,
PatchedRecipeImport, PatchedRecipeImport,
PatchedSearchPreference, PatchedSearchPreference,
PatchedShoppingList,
PatchedShoppingListEntry, PatchedShoppingListEntry,
PatchedShoppingListRecipe, PatchedShoppingListRecipe,
PatchedSpace, PatchedSpace,
@@ -165,7 +163,6 @@ import type {
SearchPreference, SearchPreference,
ServerSettings, ServerSettings,
ShareLink, ShareLink,
ShoppingList,
ShoppingListEntry, ShoppingListEntry,
ShoppingListEntryBulk, ShoppingListEntryBulk,
ShoppingListEntryBulkCreate, ShoppingListEntryBulkCreate,
@@ -327,8 +324,6 @@ import {
PaginatedRecipeOverviewListToJSON, PaginatedRecipeOverviewListToJSON,
PaginatedShoppingListEntryListFromJSON, PaginatedShoppingListEntryListFromJSON,
PaginatedShoppingListEntryListToJSON, PaginatedShoppingListEntryListToJSON,
PaginatedShoppingListListFromJSON,
PaginatedShoppingListListToJSON,
PaginatedShoppingListRecipeListFromJSON, PaginatedShoppingListRecipeListFromJSON,
PaginatedShoppingListRecipeListToJSON, PaginatedShoppingListRecipeListToJSON,
PaginatedSpaceListFromJSON, PaginatedSpaceListFromJSON,
@@ -421,8 +416,6 @@ import {
PatchedRecipeImportToJSON, PatchedRecipeImportToJSON,
PatchedSearchPreferenceFromJSON, PatchedSearchPreferenceFromJSON,
PatchedSearchPreferenceToJSON, PatchedSearchPreferenceToJSON,
PatchedShoppingListFromJSON,
PatchedShoppingListToJSON,
PatchedShoppingListEntryFromJSON, PatchedShoppingListEntryFromJSON,
PatchedShoppingListEntryToJSON, PatchedShoppingListEntryToJSON,
PatchedShoppingListRecipeFromJSON, PatchedShoppingListRecipeFromJSON,
@@ -487,8 +480,6 @@ import {
ServerSettingsToJSON, ServerSettingsToJSON,
ShareLinkFromJSON, ShareLinkFromJSON,
ShareLinkToJSON, ShareLinkToJSON,
ShoppingListFromJSON,
ShoppingListToJSON,
ShoppingListEntryFromJSON, ShoppingListEntryFromJSON,
ShoppingListEntryToJSON, ShoppingListEntryToJSON,
ShoppingListEntryBulkFromJSON, ShoppingListEntryBulkFromJSON,
@@ -1964,21 +1955,6 @@ export interface ApiShareLinkRetrieveRequest {
id: number; id: number;
} }
export interface ApiShoppingListCascadingListRequest {
id: number;
cache?: boolean;
page?: number;
pageSize?: number;
}
export interface ApiShoppingListCreateRequest {
shoppingList?: ShoppingList;
}
export interface ApiShoppingListDestroyRequest {
id: number;
}
export interface ApiShoppingListEntryBulkCreateRequest { export interface ApiShoppingListEntryBulkCreateRequest {
shoppingListEntryBulk: Omit<ShoppingListEntryBulk, 'timestamp'>; shoppingListEntryBulk: Omit<ShoppingListEntryBulk, 'timestamp'>;
} }
@@ -2012,30 +1988,6 @@ export interface ApiShoppingListEntryUpdateRequest {
shoppingListEntry: Omit<ShoppingListEntry, 'listRecipeData'|'createdBy'|'createdAt'|'updatedAt'>; shoppingListEntry: Omit<ShoppingListEntry, 'listRecipeData'|'createdBy'|'createdAt'|'updatedAt'>;
} }
export interface ApiShoppingListListRequest {
page?: number;
pageSize?: number;
}
export interface ApiShoppingListNullingListRequest {
id: number;
cache?: boolean;
page?: number;
pageSize?: number;
}
export interface ApiShoppingListPartialUpdateRequest {
id: number;
patchedShoppingList?: PatchedShoppingList;
}
export interface ApiShoppingListProtectingListRequest {
id: number;
cache?: boolean;
page?: number;
pageSize?: number;
}
export interface ApiShoppingListRecipeBulkCreateEntriesCreateRequest { export interface ApiShoppingListRecipeBulkCreateEntriesCreateRequest {
id: number; id: number;
shoppingListEntryBulkCreate: ShoppingListEntryBulkCreate; shoppingListEntryBulkCreate: ShoppingListEntryBulkCreate;
@@ -2069,15 +2021,6 @@ export interface ApiShoppingListRecipeUpdateRequest {
shoppingListRecipe: Omit<ShoppingListRecipe, 'recipeData'|'mealPlanData'|'createdBy'>; shoppingListRecipe: Omit<ShoppingListRecipe, 'recipeData'|'mealPlanData'|'createdBy'>;
} }
export interface ApiShoppingListRetrieveRequest {
id: number;
}
export interface ApiShoppingListUpdateRequest {
id: number;
shoppingList?: ShoppingList;
}
export interface ApiSpaceCreateRequest { export interface ApiSpaceCreateRequest {
space?: Omit<Space, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'|'aiMonthlyCreditsUsed'>; space?: Omit<Space, 'createdBy'|'createdAt'|'maxRecipes'|'maxFileStorageMb'|'maxUsers'|'allowSharing'|'demo'|'userCount'|'recipeCount'|'fileSizeMb'|'aiMonthlyCreditsUsed'>;
} }
@@ -14608,124 +14551,6 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value(); return await response.value();
} }
/**
* get a paginated list of objects that will be cascaded (deleted) when deleting the selected object
*/
async apiShoppingListCascadingListRaw(requestParameters: ApiShoppingListCascadingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedGenericModelReferenceList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListCascadingList().'
);
}
const queryParameters: any = {};
if (requestParameters['cache'] != null) {
queryParameters['cache'] = requestParameters['cache'];
}
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/cascading/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedGenericModelReferenceListFromJSON(jsonValue));
}
/**
* get a paginated list of objects that will be cascaded (deleted) when deleting the selected object
*/
async apiShoppingListCascadingList(requestParameters: ApiShoppingListCascadingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedGenericModelReferenceList> {
const response = await this.apiShoppingListCascadingListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListCreateRaw(requestParameters: ApiShoppingListCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/`,
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: ShoppingListToJSON(requestParameters['shoppingList']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListCreate(requestParameters: ApiShoppingListCreateRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListCreateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListDestroyRaw(requestParameters: ApiShoppingListDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListDestroy().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'DELETE',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListDestroy(requestParameters: ApiShoppingListDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.apiShoppingListDestroyRaw(requestParameters, initOverrides);
}
/** /**
* individual entries of a shopping list automatically filtered to only contain unchecked items that are not older than the shopping recent days setting to not bloat endpoint * individual entries of a shopping list automatically filtered to only contain unchecked items that are not older than the shopping recent days setting to not bloat endpoint
*/ */
@@ -15012,182 +14837,6 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value(); return await response.value();
} }
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListListRaw(requestParameters: ApiShoppingListListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedShoppingListList>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedShoppingListListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListList(requestParameters: ApiShoppingListListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedShoppingListList> {
const response = await this.apiShoppingListListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* get a paginated list of objects where the selected object will be removed whe its deleted
*/
async apiShoppingListNullingListRaw(requestParameters: ApiShoppingListNullingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedGenericModelReferenceList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListNullingList().'
);
}
const queryParameters: any = {};
if (requestParameters['cache'] != null) {
queryParameters['cache'] = requestParameters['cache'];
}
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/nulling/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedGenericModelReferenceListFromJSON(jsonValue));
}
/**
* get a paginated list of objects where the selected object will be removed whe its deleted
*/
async apiShoppingListNullingList(requestParameters: ApiShoppingListNullingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedGenericModelReferenceList> {
const response = await this.apiShoppingListNullingListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListPartialUpdateRaw(requestParameters: ApiShoppingListPartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListPartialUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'PATCH',
headers: headerParameters,
query: queryParameters,
body: PatchedShoppingListToJSON(requestParameters['patchedShoppingList']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListPartialUpdate(requestParameters: ApiShoppingListPartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListPartialUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* get a paginated list of objects that are protecting the selected object form being deleted
*/
async apiShoppingListProtectingListRaw(requestParameters: ApiShoppingListProtectingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedGenericModelReferenceList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListProtectingList().'
);
}
const queryParameters: any = {};
if (requestParameters['cache'] != null) {
queryParameters['cache'] = requestParameters['cache'];
}
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/protecting/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedGenericModelReferenceListFromJSON(jsonValue));
}
/**
* get a paginated list of objects that are protecting the selected object form being deleted
*/
async apiShoppingListProtectingList(requestParameters: ApiShoppingListProtectingListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedGenericModelReferenceList> {
const response = await this.apiShoppingListProtectingListRaw(requestParameters, initOverrides);
return await response.value();
}
/** /**
* logs request counts to redis cache total/per user/ * logs request counts to redis cache total/per user/
*/ */
@@ -15477,83 +15126,6 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value(); return await response.value();
} }
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListRetrieveRaw(requestParameters: ApiShoppingListRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListRetrieve().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListRetrieve(requestParameters: ApiShoppingListRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListRetrieveRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListUpdateRaw(requestParameters: ApiShoppingListUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShoppingList>> {
if (requestParameters['id'] == null) {
throw new runtime.RequiredError(
'id',
'Required parameter "id" was null or undefined when calling apiShoppingListUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/shopping-list/{id}/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: ShoppingListToJSON(requestParameters['shoppingList']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ShoppingListFromJSON(jsonValue));
}
/**
* logs request counts to redis cache total/per user/
*/
async apiShoppingListUpdate(requestParameters: ApiShoppingListUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ShoppingList> {
const response = await this.apiShoppingListUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/** /**
* logs request counts to redis cache total/per user/ * logs request counts to redis cache total/per user/
*/ */

View File

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

View File

@@ -1,106 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
import type { SupermarketCategory } from './SupermarketCategory';
import {
SupermarketCategoryFromJSON,
SupermarketCategoryFromJSONTyped,
SupermarketCategoryToJSON,
} from './SupermarketCategory';
/**
*
* @export
* @interface FoodShopping
*/
export interface FoodShopping {
/**
*
* @type {number}
* @memberof FoodShopping
*/
id?: number;
/**
*
* @type {string}
* @memberof FoodShopping
*/
name: string;
/**
*
* @type {string}
* @memberof FoodShopping
*/
pluralName?: string;
/**
*
* @type {SupermarketCategory}
* @memberof FoodShopping
*/
readonly supermarketCategory: SupermarketCategory;
/**
*
* @type {Array<ShoppingList>}
* @memberof FoodShopping
*/
readonly shoppingLists: Array<ShoppingList>;
}
/**
* Check if a given object implements the FoodShopping interface.
*/
export function instanceOfFoodShopping(value: object): value is FoodShopping {
if (!('name' in value) || value['name'] === undefined) return false;
if (!('supermarketCategory' in value) || value['supermarketCategory'] === undefined) return false;
if (!('shoppingLists' in value) || value['shoppingLists'] === undefined) return false;
return true;
}
export function FoodShoppingFromJSON(json: any): FoodShopping {
return FoodShoppingFromJSONTyped(json, false);
}
export function FoodShoppingFromJSONTyped(json: any, ignoreDiscriminator: boolean): FoodShopping {
if (json == null) {
return json;
}
return {
'id': json['id'] == null ? undefined : json['id'],
'name': json['name'],
'pluralName': json['plural_name'] == null ? undefined : json['plural_name'],
'supermarketCategory': SupermarketCategoryFromJSON(json['supermarket_category']),
'shoppingLists': ((json['shopping_lists'] as Array<any>).map(ShoppingListFromJSON)),
};
}
export function FoodShoppingToJSON(value?: Omit<FoodShopping, 'supermarketCategory'|'shoppingLists'> | null): any {
if (value == null) {
return value;
}
return {
'id': value['id'],
'name': value['name'],
'plural_name': value['pluralName'],
};
}

View File

@@ -1,101 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
/**
*
* @export
* @interface PaginatedShoppingListList
*/
export interface PaginatedShoppingListList {
/**
*
* @type {number}
* @memberof PaginatedShoppingListList
*/
count: number;
/**
*
* @type {string}
* @memberof PaginatedShoppingListList
*/
next?: string;
/**
*
* @type {string}
* @memberof PaginatedShoppingListList
*/
previous?: string;
/**
*
* @type {Array<ShoppingList>}
* @memberof PaginatedShoppingListList
*/
results: Array<ShoppingList>;
/**
*
* @type {Date}
* @memberof PaginatedShoppingListList
*/
timestamp?: Date;
}
/**
* Check if a given object implements the PaginatedShoppingListList interface.
*/
export function instanceOfPaginatedShoppingListList(value: object): value is PaginatedShoppingListList {
if (!('count' in value) || value['count'] === undefined) return false;
if (!('results' in value) || value['results'] === undefined) return false;
return true;
}
export function PaginatedShoppingListListFromJSON(json: any): PaginatedShoppingListList {
return PaginatedShoppingListListFromJSONTyped(json, false);
}
export function PaginatedShoppingListListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PaginatedShoppingListList {
if (json == null) {
return json;
}
return {
'count': json['count'],
'next': json['next'] == null ? undefined : json['next'],
'previous': json['previous'] == null ? undefined : json['previous'],
'results': ((json['results'] as Array<any>).map(ShoppingListFromJSON)),
'timestamp': json['timestamp'] == null ? undefined : (new Date(json['timestamp'])),
};
}
export function PaginatedShoppingListListToJSON(value?: PaginatedShoppingListList | null): any {
if (value == null) {
return value;
}
return {
'count': value['count'],
'next': value['next'],
'previous': value['previous'],
'results': ((value['results'] as Array<any>).map(ShoppingListToJSON)),
'timestamp': value['timestamp'] == null ? undefined : ((value['timestamp']).toISOString()),
};
}

View File

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

View File

@@ -1,84 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
* Adds nested create feature
* @export
* @interface PatchedShoppingList
*/
export interface PatchedShoppingList {
/**
*
* @type {number}
* @memberof PatchedShoppingList
*/
id?: number;
/**
*
* @type {string}
* @memberof PatchedShoppingList
*/
name?: string;
/**
*
* @type {string}
* @memberof PatchedShoppingList
*/
description?: string;
/**
*
* @type {string}
* @memberof PatchedShoppingList
*/
color?: string;
}
/**
* Check if a given object implements the PatchedShoppingList interface.
*/
export function instanceOfPatchedShoppingList(value: object): value is PatchedShoppingList {
return true;
}
export function PatchedShoppingListFromJSON(json: any): PatchedShoppingList {
return PatchedShoppingListFromJSONTyped(json, false);
}
export function PatchedShoppingListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PatchedShoppingList {
if (json == null) {
return json;
}
return {
'id': json['id'] == null ? undefined : json['id'],
'name': json['name'] == null ? undefined : json['name'],
'description': json['description'] == null ? undefined : json['description'],
'color': json['color'] == null ? undefined : json['color'],
};
}
export function PatchedShoppingListToJSON(value?: PatchedShoppingList | null): any {
if (value == null) {
return value;
}
return {
'id': value['id'],
'name': value['name'],
'description': value['description'],
'color': value['color'],
};
}

View File

@@ -19,18 +19,6 @@ import {
UserFromJSONTyped, UserFromJSONTyped,
UserToJSON, UserToJSON,
} from './User'; } from './User';
import type { FoodShopping } from './FoodShopping';
import {
FoodShoppingFromJSON,
FoodShoppingFromJSONTyped,
FoodShoppingToJSON,
} from './FoodShopping';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
import type { ShoppingListRecipe } from './ShoppingListRecipe'; import type { ShoppingListRecipe } from './ShoppingListRecipe';
import { import {
ShoppingListRecipeFromJSON, ShoppingListRecipeFromJSON,
@@ -43,6 +31,12 @@ import {
UnitFromJSONTyped, UnitFromJSONTyped,
UnitToJSON, UnitToJSON,
} from './Unit'; } from './Unit';
import type { Food } from './Food';
import {
FoodFromJSON,
FoodFromJSONTyped,
FoodToJSON,
} from './Food';
/** /**
* Adds nested create feature * Adds nested create feature
@@ -64,16 +58,10 @@ export interface PatchedShoppingListEntry {
listRecipe?: number; listRecipe?: number;
/** /**
* *
* @type {Array<ShoppingList>} * @type {Food}
* @memberof PatchedShoppingListEntry * @memberof PatchedShoppingListEntry
*/ */
shoppingLists?: Array<ShoppingList>; food?: Food;
/**
*
* @type {FoodShopping}
* @memberof PatchedShoppingListEntry
*/
food?: FoodShopping;
/** /**
* *
* @type {Unit} * @type {Unit}
@@ -167,8 +155,7 @@ export function PatchedShoppingListEntryFromJSONTyped(json: any, ignoreDiscrimin
'id': json['id'] == null ? undefined : json['id'], 'id': json['id'] == null ? undefined : json['id'],
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'], '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 : FoodShoppingFromJSON(json['food']),
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']), 'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
'amount': json['amount'] == null ? undefined : json['amount'], 'amount': json['amount'] == null ? undefined : json['amount'],
'order': json['order'] == null ? undefined : json['order'], 'order': json['order'] == null ? undefined : json['order'],
@@ -192,8 +179,7 @@ export function PatchedShoppingListEntryToJSON(value?: Omit<PatchedShoppingListE
'id': value['id'], 'id': value['id'],
'list_recipe': value['listRecipe'], 'list_recipe': value['listRecipe'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)), 'food': FoodToJSON(value['food']),
'food': FoodShoppingToJSON(value['food']),
'unit': UnitToJSON(value['unit']), 'unit': UnitToJSON(value['unit']),
'amount': value['amount'], 'amount': value['amount'],
'order': value['order'], 'order': value['order'],

View File

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

View File

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

View File

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

View File

@@ -1,84 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
* Adds nested create feature
* @export
* @interface ShoppingList
*/
export interface ShoppingList {
/**
*
* @type {number}
* @memberof ShoppingList
*/
id?: number;
/**
*
* @type {string}
* @memberof ShoppingList
*/
name?: string;
/**
*
* @type {string}
* @memberof ShoppingList
*/
description?: string;
/**
*
* @type {string}
* @memberof ShoppingList
*/
color?: string;
}
/**
* Check if a given object implements the ShoppingList interface.
*/
export function instanceOfShoppingList(value: object): value is ShoppingList {
return true;
}
export function ShoppingListFromJSON(json: any): ShoppingList {
return ShoppingListFromJSONTyped(json, false);
}
export function ShoppingListFromJSONTyped(json: any, ignoreDiscriminator: boolean): ShoppingList {
if (json == null) {
return json;
}
return {
'id': json['id'] == null ? undefined : json['id'],
'name': json['name'] == null ? undefined : json['name'],
'description': json['description'] == null ? undefined : json['description'],
'color': json['color'] == null ? undefined : json['color'],
};
}
export function ShoppingListToJSON(value?: ShoppingList | null): any {
if (value == null) {
return value;
}
return {
'id': value['id'],
'name': value['name'],
'description': value['description'],
'color': value['color'],
};
}

View File

@@ -19,18 +19,6 @@ import {
UserFromJSONTyped, UserFromJSONTyped,
UserToJSON, UserToJSON,
} from './User'; } from './User';
import type { FoodShopping } from './FoodShopping';
import {
FoodShoppingFromJSON,
FoodShoppingFromJSONTyped,
FoodShoppingToJSON,
} from './FoodShopping';
import type { ShoppingList } from './ShoppingList';
import {
ShoppingListFromJSON,
ShoppingListFromJSONTyped,
ShoppingListToJSON,
} from './ShoppingList';
import type { ShoppingListRecipe } from './ShoppingListRecipe'; import type { ShoppingListRecipe } from './ShoppingListRecipe';
import { import {
ShoppingListRecipeFromJSON, ShoppingListRecipeFromJSON,
@@ -43,6 +31,12 @@ import {
UnitFromJSONTyped, UnitFromJSONTyped,
UnitToJSON, UnitToJSON,
} from './Unit'; } from './Unit';
import type { Food } from './Food';
import {
FoodFromJSON,
FoodFromJSONTyped,
FoodToJSON,
} from './Food';
/** /**
* Adds nested create feature * Adds nested create feature
@@ -64,16 +58,10 @@ export interface ShoppingListEntry {
listRecipe?: number; listRecipe?: number;
/** /**
* *
* @type {Array<ShoppingList>} * @type {Food}
* @memberof ShoppingListEntry * @memberof ShoppingListEntry
*/ */
shoppingLists?: Array<ShoppingList>; food: Food | null;
/**
*
* @type {FoodShopping}
* @memberof ShoppingListEntry
*/
food: FoodShopping | null;
/** /**
* *
* @type {Unit} * @type {Unit}
@@ -173,8 +161,7 @@ export function ShoppingListEntryFromJSONTyped(json: any, ignoreDiscriminator: b
'id': json['id'] == null ? undefined : json['id'], 'id': json['id'] == null ? undefined : json['id'],
'listRecipe': json['list_recipe'] == null ? undefined : json['list_recipe'], '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': FoodShoppingFromJSON(json['food']),
'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']), 'unit': json['unit'] == null ? undefined : UnitFromJSON(json['unit']),
'amount': json['amount'], 'amount': json['amount'],
'order': json['order'] == null ? undefined : json['order'], 'order': json['order'] == null ? undefined : json['order'],
@@ -198,8 +185,7 @@ export function ShoppingListEntryToJSON(value?: Omit<ShoppingListEntry, 'listRec
'id': value['id'], 'id': value['id'],
'list_recipe': value['listRecipe'], 'list_recipe': value['listRecipe'],
'shopping_lists': value['shoppingLists'] == null ? undefined : ((value['shoppingLists'] as Array<any>).map(ShoppingListToJSON)), 'food': FoodToJSON(value['food']),
'food': FoodShoppingToJSON(value['food']),
'unit': UnitToJSON(value['unit']), 'unit': UnitToJSON(value['unit']),
'amount': value['amount'], 'amount': value['amount'],
'order': value['order'], 'order': value['order'],

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,6 @@ export * from './FdcQueryFoods';
export * from './Food'; export * from './Food';
export * from './FoodBatchUpdate'; export * from './FoodBatchUpdate';
export * from './FoodInheritField'; export * from './FoodInheritField';
export * from './FoodShopping';
export * from './FoodShoppingUpdate'; export * from './FoodShoppingUpdate';
export * from './FoodSimple'; export * from './FoodSimple';
export * from './GenericModelReference'; export * from './GenericModelReference';
@@ -93,7 +92,6 @@ export * from './PaginatedRecipeBookList';
export * from './PaginatedRecipeImportList'; export * from './PaginatedRecipeImportList';
export * from './PaginatedRecipeOverviewList'; export * from './PaginatedRecipeOverviewList';
export * from './PaginatedShoppingListEntryList'; export * from './PaginatedShoppingListEntryList';
export * from './PaginatedShoppingListList';
export * from './PaginatedShoppingListRecipeList'; export * from './PaginatedShoppingListRecipeList';
export * from './PaginatedSpaceList'; export * from './PaginatedSpaceList';
export * from './PaginatedStepList'; export * from './PaginatedStepList';
@@ -140,7 +138,6 @@ export * from './PatchedRecipeBook';
export * from './PatchedRecipeBookEntry'; export * from './PatchedRecipeBookEntry';
export * from './PatchedRecipeImport'; export * from './PatchedRecipeImport';
export * from './PatchedSearchPreference'; export * from './PatchedSearchPreference';
export * from './PatchedShoppingList';
export * from './PatchedShoppingListEntry'; export * from './PatchedShoppingListEntry';
export * from './PatchedShoppingListRecipe'; export * from './PatchedShoppingListRecipe';
export * from './PatchedSpace'; export * from './PatchedSpace';
@@ -175,7 +172,6 @@ export * from './SearchFields';
export * from './SearchPreference'; export * from './SearchPreference';
export * from './ServerSettings'; export * from './ServerSettings';
export * from './ShareLink'; export * from './ShareLink';
export * from './ShoppingList';
export * from './ShoppingListEntry'; export * from './ShoppingListEntry';
export * from './ShoppingListEntryBulk'; export * from './ShoppingListEntryBulk';
export * from './ShoppingListEntryBulkCreate'; export * from './ShoppingListEntryBulkCreate';

View File

@@ -31,7 +31,6 @@
</v-row> </v-row>
<v-row dense> <v-row dense>
<database-model-col model="Supermarket"></database-model-col> <database-model-col model="Supermarket"></database-model-col>
<database-model-col model="ShoppingList"></database-model-col>
<database-model-col model="SupermarketCategory"></database-model-col> <database-model-col model="SupermarketCategory"></database-model-col>
<database-model-col model="MealType"></database-model-col> <database-model-col model="MealType"></database-model-col>
</v-row> </v-row>

View File

@@ -95,9 +95,6 @@
<v-chip label v-if="item.id == useUserPreferenceStore().activeSpace.id!" color="success">{{ $t('Active') }}</v-chip> <v-chip label v-if="item.id == useUserPreferenceStore().activeSpace.id!" color="success">{{ $t('Active') }}</v-chip>
<v-chip label v-else color="info" @click="useUserPreferenceStore().switchSpace(item)">{{ $t('Select') }}</v-chip> <v-chip label v-else color="info" @click="useUserPreferenceStore().switchSpace(item)">{{ $t('Select') }}</v-chip>
</template> </template>
<template v-slot:item.color="{ item }">
<v-chip label :color="item.color">{{ item.color }}</v-chip>
</template>
<template v-slot:item.action="{ item }"> <template v-slot:item.action="{ item }">
<v-btn class="float-right" icon="$menu" variant="plain"> <v-btn class="float-right" icon="$menu" variant="plain">
<v-icon icon="$menu"></v-icon> <v-icon icon="$menu"></v-icon>

View File

@@ -1,6 +1,6 @@
import {acceptHMRUpdate, defineStore} from "pinia" import {acceptHMRUpdate, defineStore} from "pinia"
import {ApiApi, ApiShoppingListEntryListRequest, Food, Recipe, ShoppingListEntry, ShoppingListEntryBulk, ShoppingListRecipe, Supermarket, SupermarketCategory} from "@/openapi"; import {ApiApi, ApiShoppingListEntryListRequest, Food, Recipe, ShoppingListEntry, ShoppingListEntryBulk, ShoppingListRecipe, Supermarket, SupermarketCategory} from "@/openapi";
import {computed, ref, shallowRef, triggerRef} from "vue"; import {computed, ref} from "vue";
import { import {
IShoppingExportEntry, IShoppingExportEntry,
IShoppingList, IShoppingList,
@@ -25,6 +25,14 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let supermarketCategories = ref([] as SupermarketCategory[]) let supermarketCategories = ref([] as SupermarketCategory[])
let supermarkets = ref([] as Supermarket[]) let supermarkets = ref([] as Supermarket[])
let stats = ref({
countChecked: 0,
countUnchecked: 0,
countCheckedFood: 0,
countUncheckedFood: 0,
countUncheckedDelayed: 0,
} as ShoppingListStats)
// internal // internal
let currentlyUpdating = ref(false) let currentlyUpdating = ref(false)
let initialized = ref(false) let initialized = ref(false)
@@ -33,25 +41,28 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let autoSyncHasFocus = ref(true) let autoSyncHasFocus = ref(true)
let autoSyncTimeoutId = ref(0) let autoSyncTimeoutId = ref(0)
let undoStack = shallowRef([] as ShoppingOperationHistoryEntry[]) let undoStack = ref([] as ShoppingOperationHistoryEntry[])
let queueTimeoutId = ref(-1) let queueTimeoutId = ref(-1)
let itemCheckSyncQueue = shallowRef([] as IShoppingSyncQueueEntry[]) let itemCheckSyncQueue = ref([] as IShoppingSyncQueueEntry[])
let syncQueueRunning = ref(false)
let entriesByGroup = shallowRef([] as IShoppingListCategory[])
/** /**
* build a multi-level data structure ready for display from shopping list entries * build a multi-level data structure ready for display from shopping list entries
* group by selected grouping key * group by selected grouping key
*/ */
function updateEntriesStructure() { const getEntriesByGroup = computed(() => {
stats.value = {
countChecked: 0,
countUnchecked: 0,
countCheckedFood: 0,
countUncheckedFood: 0,
countUncheckedDelayed: 0,
} as ShoppingListStats
let structure = {} as IShoppingList let structure = {} as IShoppingList
structure.categories = new Map<string, IShoppingListCategory> structure.categories = new Map<string, IShoppingListCategory>
const deviceSettings = useUserPreferenceStore().deviceSettings if (useUserPreferenceStore().deviceSettings.shopping_selected_grouping === ShoppingGroupingOptions.CATEGORY && useUserPreferenceStore().deviceSettings.shopping_selected_supermarket != null) {
useUserPreferenceStore().deviceSettings.shopping_selected_supermarket.categoryToSupermarket.forEach(cTS => {
if (deviceSettings.shopping_selected_grouping === ShoppingGroupingOptions.CATEGORY && deviceSettings.shopping_selected_supermarket != null) {
deviceSettings.shopping_selected_supermarket.categoryToSupermarket.forEach(cTS => {
structure.categories.set(cTS.category.name, {'name': cTS.category.name, 'foods': new Map<number, IShoppingListFood>} as IShoppingListCategory) structure.categories.set(cTS.category.name, {'name': cTS.category.name, 'foods': new Map<number, IShoppingListFood>} as IShoppingListCategory)
}) })
} }
@@ -60,9 +71,46 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
// build structure // build structure
entries.value.forEach(shoppingListEntry => { entries.value.forEach(shoppingListEntry => {
if (isEntryVisible(shoppingListEntry, deviceSettings)) {
structure = updateEntryInStructure(structure, shoppingListEntry) structure = updateEntryInStructure(structure, shoppingListEntry)
})
// statistics for UI conditions and display
structure.categories.forEach(category => {
let categoryStats = {
countChecked: 0,
countUnchecked: 0,
countCheckedFood: 0,
countUncheckedFood: 0,
countUncheckedDelayed: 0,
} as ShoppingListStats
category.foods.forEach(food => {
let food_checked = true
food.entries.forEach(entry => {
if (entry.checked) {
categoryStats.countChecked++
} else {
if (isDelayed(entry)) {
categoryStats.countUncheckedDelayed++
} else {
categoryStats.countUnchecked++
} }
}
})
if (food_checked) {
categoryStats.countCheckedFood++
} else {
categoryStats.countUncheckedFood++
}
})
category.stats = categoryStats
stats.value.countChecked += categoryStats.countChecked
stats.value.countUnchecked += categoryStats.countUnchecked
stats.value.countCheckedFood += categoryStats.countCheckedFood
stats.value.countUncheckedFood += categoryStats.countUncheckedFood
}) })
// ordering // ordering
@@ -73,27 +121,10 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
} }
structure.categories.forEach(category => { structure.categories.forEach(category => {
if (category.foods.size > 0) {
orderedStructure.push(category) orderedStructure.push(category)
}
}) })
entriesByGroup.value = orderedStructure return orderedStructure
}
/**
* get the total number of foods in the shopping list
* since entries are always grouped by food, it makes no sense to display the entry count anywhere
*/
let totalFoods = computed(() => {
let count = 0
if (initialized.value) {
entriesByGroup.value.forEach(category => {
count += category.foods.size
})
}
return count
}) })
/** /**
@@ -104,7 +135,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
function getFlatEntries() { function getFlatEntries() {
let items: IShoppingExportEntry[] = [] let items: IShoppingExportEntry[] = []
entriesByGroup.value.forEach(shoppingListEntry => { getEntriesByGroup.value.forEach(shoppingListEntry => {
shoppingListEntry.foods.forEach(food => { shoppingListEntry.foods.forEach(food => {
food.entries.forEach(entry => { food.entries.forEach(entry => {
items.push({ items.push({
@@ -144,7 +175,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
function hasFailedItems() { function hasFailedItems() {
for (let i in itemCheckSyncQueue.value) { for (let i in itemCheckSyncQueue.value) {
if (itemCheckSyncQueue.value[i]['status'] === 'syncing_failed_before' || itemCheckSyncQueue.value[i]['status'] === 'waiting_failed_before') { if (itemCheckSyncQueue.value[i]['status'] === 'syncing_failed_before' || itemCheckSyncQueue.value[i]['status'] === 'waiting_failed_before') {
return !syncQueueRunning.value return true
} }
} }
return false return false
@@ -166,7 +197,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
} else { } else {
// only clear local entries when not given a meal plan to not accidentally filter the shopping list // only clear local entries when not given a meal plan to not accidentally filter the shopping list
entries.value = new Map<number, ShoppingListEntry> entries.value = new Map<number, ShoppingListEntry>
initialized.value = false
} }
recLoadShoppingListEntries(requestParameters) recLoadShoppingListEntries(requestParameters)
@@ -191,30 +221,17 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
*/ */
function recLoadShoppingListEntries(requestParameters: ApiShoppingListEntryListRequest) { function recLoadShoppingListEntries(requestParameters: ApiShoppingListEntryListRequest) {
let api = new ApiApi() let api = new ApiApi()
return api.apiShoppingListEntryList(requestParameters).then((r) => { api.apiShoppingListEntryList(requestParameters).then((r) => {
let promises = [] as Promise<any>[]
let newMap = new Map<number, ShoppingListEntry>()
r.results.forEach((e) => { r.results.forEach((e) => {
newMap.set(e.id!, e) entries.value.set(e.id!, e)
}) })
// bulk assign to avoid unnecessary reactivity updates
entries.value = new Map([...entries.value, ...newMap])
if (requestParameters.page == 1) {
if (r.next) { if (r.next) {
while (Math.ceil(r.count / requestParameters.pageSize) > requestParameters.page) {
requestParameters.page = requestParameters.page + 1 requestParameters.page = requestParameters.page + 1
promises.push(recLoadShoppingListEntries(requestParameters)) recLoadShoppingListEntries(requestParameters)
} } else {
}
Promise.allSettled(promises).then(() => {
updateEntriesStructure()
currentlyUpdating.value = false currentlyUpdating.value = false
initialized.value = true initialized.value = true
})
} }
}).catch((err) => { }).catch((err) => {
currentlyUpdating.value = false currentlyUpdating.value = false
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err) useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
@@ -234,9 +251,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
r.results.forEach((e) => { r.results.forEach((e) => {
entries.value.set(e.id!, e) entries.value.set(e.id!, e)
}) })
if(r.results.length > 0){
updateEntriesStructure()
}
currentlyUpdating.value = false currentlyUpdating.value = false
}).catch((err: any) => { }).catch((err: any) => {
currentlyUpdating.value = false currentlyUpdating.value = false
@@ -253,7 +267,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
const api = new ApiApi() const api = new ApiApi()
return api.apiShoppingListEntryCreate({shoppingListEntry: object}).then((r) => { return api.apiShoppingListEntryCreate({shoppingListEntry: object}).then((r) => {
entries.value.set(r.id!, r) entries.value.set(r.id!, r)
updateEntriesStructure()
if (undo) { if (undo) {
registerChange("CREATE", [r]) registerChange("CREATE", [r])
} }
@@ -286,7 +299,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
}) })
} }
/** /**
* delete shopping list entry object from DB and store * delete shopping list entry object from DB and store
* @param object entry object to delete * @param object entry object to delete
@@ -296,19 +308,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
const api = new ApiApi() const api = new ApiApi()
return api.apiShoppingListEntryDestroy({id: object.id!}).then((r) => { return api.apiShoppingListEntryDestroy({id: object.id!}).then((r) => {
entries.value.delete(object.id!) 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) { if (undo) {
registerChange("DESTROY", [object]) registerChange("DESTROY", [object])
} }
@@ -332,32 +331,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
return recipes 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 * 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 * if a group does not yet exist and the sorting is not set to category with selected supermarket only, it will be created
@@ -365,8 +339,23 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
* @param entry * @param entry
*/ */
function updateEntryInStructure(structure: IShoppingList, entry: ShoppingListEntry) { function updateEntryInStructure(structure: IShoppingList, entry: ShoppingListEntry) {
let groupingKey = UNDEFINED_CATEGORY
let group = useUserPreferenceStore().deviceSettings.shopping_selected_grouping let group = useUserPreferenceStore().deviceSettings.shopping_selected_grouping
let groupingKey = getEntryCategoryKey(entry)
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)
}
}
}
if (!structure.categories.has(groupingKey) && !(group == ShoppingGroupingOptions.CATEGORY && useUserPreferenceStore().deviceSettings.shopping_show_selected_supermarket_only)) { 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) structure.categories.set(groupingKey, {'name': groupingKey, 'foods': new Map<number, IShoppingListFood>} as IShoppingListCategory)
@@ -394,18 +383,19 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
if (undo) { if (undo) {
registerChange((checked ? 'CHECKED' : 'UNCHECKED'), entries) registerChange((checked ? 'CHECKED' : 'UNCHECKED'), entries)
} }
let entryIdList: number[] = [] let entryIdList: number[] = []
entries.forEach(entry => { entries.forEach(entry => {
entry.checked = checked entry.checked = checked
entryIdList.push(entry.id!) entryIdList.push(entry.id!)
}) })
itemCheckSyncQueue.value.push({ itemCheckSyncQueue.value.push({
ids: entryIdList, ids: entryIdList,
checked: checked, checked: checked,
status: 'waiting', status: 'waiting',
} as IShoppingSyncQueueEntry) } as IShoppingSyncQueueEntry)
runSyncQueue(5)
runSyncQueue(100)
} }
/** /**
@@ -419,17 +409,15 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let api = new ApiApi() let api = new ApiApi()
let promises: Promise<void>[] = [] let promises: Promise<void>[] = []
let updatedEntries = new Map<number, ShoppingListEntry>()
itemCheckSyncQueue.value.forEach((entry, index) => { itemCheckSyncQueue.value.forEach((entry, index) => {
entry['status'] = ((entry['status'] === 'waiting_failed_before') ? 'syncing_failed_before' : 'syncing') entry['status'] = ((entry['status'] === 'waiting') ? 'syncing' : 'syncing_failed_before')
syncQueueRunning.value = true
let p = api.apiShoppingListEntryBulkCreate({shoppingListEntryBulk: entry}, {}).then((r) => { let p = api.apiShoppingListEntryBulkCreate({shoppingListEntryBulk: entry}, {}).then((r) => {
entry.ids.forEach(id => { entry.ids.forEach(id => {
let e = entries.value.get(id) let e = entries.value.get(id)
if (e) {
e.updatedAt = r.timestamp e.updatedAt = r.timestamp
updatedEntries.set(id, e) e.checked = r.checked
} entries.value.set(id, e)
}) })
itemCheckSyncQueue.value.splice(index, 1) itemCheckSyncQueue.value.splice(index, 1)
}).catch((err) => { }).catch((err) => {
@@ -444,10 +432,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
}) })
Promise.allSettled(promises).finally(() => { Promise.allSettled(promises).finally(() => {
entries.value = new Map([...entries.value, ...updatedEntries])
syncQueueRunning.value = false
//TODO proper function to splice/update structure as needed
useShoppingStore().updateEntriesStructure()
if (itemCheckSyncQueue.value.length > 0) { if (itemCheckSyncQueue.value.length > 0) {
runSyncQueue(500) runSyncQueue(500)
} }
@@ -595,8 +579,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
entries, entries,
supermarkets, supermarkets,
supermarketCategories, supermarketCategories,
updateEntriesStructure, getEntriesByGroup,
entriesByGroup,
autoSyncTimeoutId, autoSyncTimeoutId,
autoSyncHasFocus, autoSyncHasFocus,
autoSyncLastTimestamp, autoSyncLastTimestamp,
@@ -606,7 +589,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
hasFailedItems, hasFailedItems,
itemCheckSyncQueue, itemCheckSyncQueue,
undoStack, undoStack,
totalFoods, stats,
refreshFromAPI, refreshFromAPI,
autoSync, autoSync,
createObject, createObject,

View File

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

View File

@@ -7,7 +7,7 @@ import {
MealPlan, MealPlan,
MealType, MealType,
Property, PropertyType, Property, PropertyType,
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchFields, ShoppingList, ShoppingListEntry, Space, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchFields, ShoppingListEntry, Space,
Step, Step,
Supermarket, Supermarket,
SupermarketCategory, Sync, SyncLog, SupermarketCategory, Sync, SyncLog,
@@ -144,7 +144,6 @@ export type EditorSupportedModels =
| 'Automation' | 'Automation'
| 'Keyword' | 'Keyword'
| 'UserFile' | 'UserFile'
| 'ShoppingList'
| 'ShoppingListEntry' | 'ShoppingListEntry'
| 'User' | 'User'
| 'RecipeBook' | 'RecipeBook'
@@ -183,7 +182,6 @@ export type EditorSupportedTypes =
| Automation | Automation
| Keyword | Keyword
| UserFile | UserFile
| ShoppingList
| ShoppingListEntry | ShoppingListEntry
| User | User
| RecipeBook | RecipeBook
@@ -486,28 +484,6 @@ export const TSupermarketCategory = {
} as Model } as Model
registerModel(TSupermarketCategory) registerModel(TSupermarketCategory)
export const TShoppingList = {
name: 'ShoppingList',
localizationKey: 'ShoppingList',
localizationKeyDescription: 'ShoppingListHelp',
icon: 'fa-solid fa-list-check',
editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/ShoppingListEditor.vue`)),
disableListView: true,
isPaginated: true,
toStringKeys: ['name'],
tableHeaders: [
{title: 'Name', key: 'name'},
{title: 'Color', key: 'color'},
{title: 'Description', key: 'description'},
{title: 'Actions', key: 'action', align: 'end'},
]
} as Model
registerModel(TShoppingList)
export const TShoppingListEntry = { export const TShoppingListEntry = {
name: 'ShoppingListEntry', name: 'ShoppingListEntry',
localizationKey: 'ShoppingListEntry', localizationKey: 'ShoppingListEntry',

View File

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

View File

@@ -1,4 +1,4 @@
import {ShoppingList, Supermarket} from "@/openapi"; import {Supermarket} from "@/openapi";
export type DeviceSettings = { export type DeviceSettings = {
shopping_show_checked_entries: boolean shopping_show_checked_entries: boolean
@@ -6,7 +6,6 @@ export type DeviceSettings = {
shopping_show_selected_supermarket_only: boolean shopping_show_selected_supermarket_only: boolean
shopping_selected_grouping: string shopping_selected_grouping: string
shopping_selected_supermarket: Supermarket | null shopping_selected_supermarket: Supermarket | null
shopping_selected_shopping_list: number[]
shopping_item_info_created_by: boolean shopping_item_info_created_by: boolean
shopping_item_info_mealplan: boolean shopping_item_info_mealplan: boolean
shopping_item_info_recipe: boolean shopping_item_info_recipe: boolean

View File

@@ -18,15 +18,6 @@ export function isEntryVisible(entry: ShoppingListEntry, deviceSettings: DeviceS
if (entry.checked && !deviceSettings.shopping_show_checked_entries) { if (entry.checked && !deviceSettings.shopping_show_checked_entries) {
entryVisible = false entryVisible = false
} }
// if no list is selected show all entries
// if -1 is selected show entries without shopping lists
// otherwise check if at least one of the entries lists is selected
if(deviceSettings.shopping_selected_shopping_list.length > 0){
if(!(deviceSettings.shopping_selected_shopping_list.includes(-1) && entry.shoppingLists?.length == 0) && !deviceSettings.shopping_selected_shopping_list.some(sl => (entry.shoppingLists?.findIndex(eSl => eSl.id == sl) != -1))){
entryVisible = false
}
}
return entryVisible return entryVisible
} }
@@ -69,15 +60,16 @@ export function isShoppingListFoodDelayed(slf: IShoppingListFood) {
* @param category * @param category
*/ */
export function isShoppingCategoryVisible(category: IShoppingListCategory) { export function isShoppingCategoryVisible(category: IShoppingListCategory) {
console.log('checking if category is visible') let entryCount = category.stats.countUnchecked
let categoryVisible = false
category.foods.forEach(food => {
if(isShoppingListFoodVisible(food, useUserPreferenceStore().deviceSettings)){
categoryVisible = true
}
})
return categoryVisible if (useUserPreferenceStore().deviceSettings.shopping_show_checked_entries) {
entryCount += category.stats.countChecked
}
if (useUserPreferenceStore().deviceSettings.shopping_show_delayed_entries) {
entryCount += category.stats.countUncheckedDelayed
}
return entryCount > 0
} }
// -------------- SPACE RELATED ---------------------- // -------------- SPACE RELATED ----------------------