Compare commits

..

19 Commits

Author SHA1 Message Date
dependabot[bot]
7a72535fe4 Bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-01 00:01:54 +00:00
vabene1111
28e554d04b badge when list selected 2025-12-21 14:57:29 +01:00
vabene1111
5040caf91c fixed another mealie edge case 2025-12-20 18:32:16 +01:00
vabene1111
c5fcfd07a7 quick optimizations 2025-12-05 08:20:19 +01:00
vabene1111
a1a172e223 WIP 2025-12-04 09:25:16 +01:00
vabene1111
0039654d40 default list when adding trough mealplan/recipe 2025-12-04 08:07:32 +01:00
vabene1111
17de37b9fc fixed category change entry updaet 2025-12-04 07:46:56 +01:00
vabene1111
d0856ce3b7 category change fix 2025-12-04 07:19:58 +01:00
vabene1111
24426c2b7e Merge pull request #4282 from TandoorRecipes/dependabot/pip/django-5.2.9
Bump django from 5.2.8 to 5.2.9
2025-12-03 21:24:45 +01:00
vabene1111
5380b7d697 fixesa 2025-12-03 21:22:49 +01:00
vabene1111
1d0488fbb0 temporary fix 2025-12-03 18:09:05 +01:00
vabene1111
2213346297 Merge branch 'feature/shopping' into develop
# Conflicts:
#	vue3/src/locales/nl.json
#	vue3/src/locales/sl.json
#	vue3/src/locales/uk.json
2025-12-03 17:53:17 +01:00
dependabot[bot]
126f21842f Bump django from 5.2.8 to 5.2.9
Bumps [django](https://github.com/django/django) from 5.2.8 to 5.2.9.
- [Commits](https://github.com/django/django/compare/5.2.8...5.2.9)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 5.2.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-03 14:22:12 +00:00
SerhiiOS
84898b09f2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (874 of 874 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/uk/
2025-12-02 08:03:46 +00:00
Matjaž T.
d4ded02c2a Translated using Weblate (Slovenian)
Currently translated at 100.0% (874 of 874 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/sl/
2025-12-02 08:03:46 +00:00
Justin Straver
8db294255e Translated using Weblate (Dutch)
Currently translated at 99.8% (873 of 874 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/nl/
2025-12-02 08:03:46 +00:00
SerhiiOS
c99d13e2e7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (488 of 488 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/uk/
2025-12-02 08:03:45 +00:00
Matjaž T.
59040f4cdf Translated using Weblate (Slovenian)
Currently translated at 100.0% (871 of 871 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/sl/
2025-12-01 06:08:34 +00:00
Matjaž T.
af476480c1 Translated using Weblate (Slovenian)
Currently translated at 100.0% (373 of 373 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/sl/
2025-12-01 06:08:34 +00:00
23 changed files with 2878 additions and 2691 deletions

View File

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

View File

@@ -177,8 +177,9 @@ class RecipeShoppingEditor():
existing = self._shopping_list_recipe.entries.filter(ingredient__in=ingredients).values_list('ingredient__pk', flat=True)
add_ingredients = ingredients.exclude(id__in=existing)
entries = []
for i in [x for x in add_ingredients if x.food]:
ShoppingListEntry.objects.create(
entry = ShoppingListEntry(
list_recipe=self._shopping_list_recipe,
food=i.food,
unit=i.unit,
@@ -187,6 +188,12 @@ class RecipeShoppingEditor():
created_by=self.created_by,
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
def _delete_ingredients(self, ingredients=None):

View File

@@ -197,7 +197,7 @@ class Mealie1(Integration):
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))
elif i['note'].strip():
elif i['note'] and i['note'].strip():
amount, unit, food, note = ingredient_parser.parse(i['note'].strip())
f = ingredient_parser.get_food(food)
u = ingredient_parser.get_unit(unit)

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import traceback
import uuid
from datetime import datetime, timedelta
from decimal import Decimal
@@ -188,14 +189,22 @@ class SpaceFilterSerializer(serializers.ListSerializer):
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):
data = [d for d in iterable if d.userspace.space.id == self.context['request'].space.id]
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()
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]
else:
@@ -772,7 +781,7 @@ class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
fields = ('id', 'category', 'supermarket', 'order')
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataModelMixin):
class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, WritableNestedModelSerializer, OpenDataModelMixin):
category_to_supermarket = SupermarketCategoryRelationSerializer(many=True, read_only=True)
shopping_lists = ShoppingListSerializer(many=True, required=False)
@@ -1430,8 +1439,17 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
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):
food = FoodSimpleSerializer(allow_null=True)
food = FoodShoppingSerializer(allow_null=True)
unit = UnitSerializer(allow_null=True, required=False)
shopping_lists = ShoppingListSerializer(many=True, required=False)
list_recipe_data = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)

View File

@@ -1992,19 +1992,22 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet):
if serializer.is_valid():
entries = []
for e in serializer.validated_data['entries']:
entries.append(
ShoppingListEntry(
list_recipe_id=obj.pk,
amount=e['amount'],
unit_id=e['unit_id'],
food_id=e['food_id'],
ingredient_id=e['ingredient_id'],
created_by_id=request.user.id,
space_id=request.space.id,
)
entry = ShoppingListEntry(
list_recipe_id=obj.pk,
amount=e['amount'],
unit_id=e['unit_id'],
food_id=e['food_id'],
ingredient_id=e['ingredient_id'],
created_by_id=request.user.id,
space_id=request.space.id,
)
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())
ConnectorManager.add_work(ActionType.CREATED, *entries)
return Response(serializer.validated_data)
else:
@@ -2041,10 +2044,12 @@ class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space)
# select_related("list_recipe")
self.queryset = self.queryset.filter(
Q(created_by=self.request.user)
| Q(created_by__in=list(self.request.user.get_shopping_share()))).prefetch_related('created_by',
'food',
'food__shopping_lists',
'shopping_lists',
'unit',
'list_recipe',
@@ -2052,6 +2057,7 @@ class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet):
'list_recipe__recipe__created_by',
'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__keywords',

View File

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

View File

@@ -174,6 +174,8 @@ const isShoppingLineDelayed = computed(() => {
function categoryUpdate(category: SupermarketCategory) {
const api = new ApiApi()
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 => {
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS)
}).catch(err => {

View File

@@ -1,6 +1,9 @@
<template>
<v-list-item class="swipe-container border-t-sm mt-0 mb-0 pt-0 pb-0 pe-0 pa-0 shopping-border" :id="itemContainerId" @touchend="handleSwipe()" @click="dialog = true;"
<v-list-item class="swipe-container border-t-sm mt-0 mb-0 pt-0 pb-0 pe-0 pa-0 shopping-border"
:id="itemContainerId"
@touchend="handleSwipe()"
@click="dialog = true;"
:value="shoppingListFood"
>
<!-- <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>-->
@@ -34,13 +37,18 @@
</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]>
<div class="ps-3 pe-3" @click.native.stop="useShoppingStore().setEntriesCheckedState(entries, !isChecked, true);">
<v-btn color="success" size="large"
:class="{'btn-success': !isChecked, 'btn-warning': isChecked}" :icon="actionButtonIcon" variant="plain">
</v-btn>
</div>
<!-- <i class="d-print-none fa-fw fas" :class="{'fa-check': !isChecked , 'fa-cart-plus': isChecked }"></i>-->
</template>
<!-- <div class="swipe-action bg-primary justify-content-end">-->
@@ -71,9 +79,11 @@ const emit = defineEmits(['clicked'])
const props = defineProps({
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 selectBtnSlot = ref(useUserPreferenceStore().userSettings.leftHanded ? 'append' : 'prepend')
const dialog = ref(false)

View File

@@ -31,7 +31,7 @@
<v-divider></v-divider>
<v-list-item>
<v-select hide-details :items="groupingOptionsItems" v-model="useUserPreferenceStore().deviceSettings.shopping_selected_grouping"
@update:modelValue="useShoppingStore().updateEntriesStructure()" :label="$t('GroupBy')">
:label="$t('GroupBy')">
</v-select>
</v-list-item>
<v-list-item v-if="useUserPreferenceStore().deviceSettings.shopping_selected_grouping == ShoppingGroupingOptions.CATEGORY">
@@ -69,14 +69,14 @@
</v-list>
</v-menu>
<!-- <v-btn height="100%" rounded="0" variant="plain">-->
<!-- <i class="fa-solid fa-download"></i>-->
<!-- <shopping-export-dialog></shopping-export-dialog>-->
<!-- </v-btn>-->
<!-- <v-btn height="100%" rounded="0" variant="plain">-->
<!-- <i class="fa-solid fa-download"></i>-->
<!-- <shopping-export-dialog></shopping-export-dialog>-->
<!-- </v-btn>-->
<!-- <v-btn height="100%" rounded="0" variant="plain" @click="useShoppingStore().undoChange()">-->
<!-- <i class="fa-solid fa-arrow-rotate-left"></i>-->
<!-- </v-btn>-->
<!-- <v-btn height="100%" rounded="0" variant="plain" @click="useShoppingStore().undoChange()">-->
<!-- <i class="fa-solid fa-arrow-rotate-left"></i>-->
<!-- </v-btn>-->
</v-tabs>
@@ -116,18 +116,29 @@
</v-menu>
</v-chip>
<v-chip label density="compact" variant="outlined" style="max-width: 50%;" :prepend-icon="TShoppingList.icon" append-icon="fa-solid fa-caret-down">
<template v-if="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list.length > 0">
{{ shoppingLists.filter(sl => useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list.includes(sl.id)).flatMap(sl => sl.name).join(', ') }}
<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"
@update:selected="useShoppingStore().updateEntriesStructure()" select-strategy="leaf">
<v-list-item @click="useUserPreferenceStore().deviceSettings.shopping_selected_shopping_list = []; useShoppingStore().updateEntriesStructure()">
<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>
@@ -165,7 +176,7 @@
<v-skeleton-loader type="list-item"></v-skeleton-loader>
<v-skeleton-loader type="list-item"></v-skeleton-loader>
</v-list>
<v-list class="mt-3" density="compact" v-else>
<v-list class="mt-3" density="compact" v-model:selected="selectedLines" select-strategy="leaf" v-else>
<template v-for="category in useShoppingStore().entriesByGroup" :key="category.name">
@@ -303,14 +314,14 @@
<script setup lang="ts">
import {computed, onMounted, ref, toRef} from "vue";
import {computed, onMounted, ref, shallowRef, toRef, watch} from "vue";
import {useShoppingStore} from "@/stores/ShoppingStore";
import {ApiApi, Recipe, ResponseError, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Supermarket} from "@/openapi";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
import ShoppingLineItem from "@/components/display/ShoppingLineItem.vue";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {ShoppingGroupingOptions} from "@/types/Shopping";
import {IShoppingListFood, ShoppingGroupingOptions} from "@/types/Shopping";
import {useI18n} from "vue-i18n";
import NumberScalerDialog from "@/components/inputs/NumberScalerDialog.vue";
import SupermarketEditor from "@/components/model_editors/SupermarketEditor.vue";
@@ -332,6 +343,8 @@ const supermarkets = ref([] as Supermarket[])
const shoppingLists = ref([] as ShoppingList[])
const manualAddRecipe = ref<undefined | Recipe>(undefined)
const selectedLines = shallowRef([] as IShoppingListFood[])
/**
* VSelect items for shopping list grouping options with localized names
*/
@@ -343,6 +356,10 @@ const groupingOptionsItems = computed(() => {
return items
})
watch(() => useUserPreferenceStore().deviceSettings, () => {
useShoppingStore().updateEntriesStructure()
}, {deep: true})
onMounted(() => {
addEventListener("visibilitychange", (event) => {
useShoppingStore().autoSyncHasFocus = (document.visibilityState === 'visible')

View File

@@ -377,6 +377,7 @@
"NoUnit": "Keine Einheit",
"No_ID": "ID nicht gefunden und kann nicht gelöscht werden.",
"No_Results": "Keine Ergebnisse",
"None": "Keine",
"NotFound": "Nicht gefunden",
"NotFoundHelp": "Die gesuchte Seite konnte nicht gefunden werden.",
"NotInShopping": "{food} befindet sich nicht auf Ihrer Einkaufsliste.",

View File

@@ -375,6 +375,7 @@
"NoUnit": "No Unit",
"No_ID": "ID not found, cannot delete.",
"No_Results": "No Results",
"None": "None",
"NotFound": "Not found",
"NotFoundHelp": "The page or object you are looking for could not be found.",
"NotInShopping": "{food} is not in your shopping list.",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ models/FdcQueryFoods.ts
models/Food.ts
models/FoodBatchUpdate.ts
models/FoodInheritField.ts
models/FoodShopping.ts
models/FoodShoppingUpdate.ts
models/FoodSimple.ts
models/GenericModelReference.ts

View File

@@ -0,0 +1,106 @@
/* 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

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

View File

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

View File

@@ -29,6 +29,7 @@ export * from './FdcQueryFoods';
export * from './Food';
export * from './FoodBatchUpdate';
export * from './FoodInheritField';
export * from './FoodShopping';
export * from './FoodShoppingUpdate';
export * from './FoodSimple';
export * from './GenericModelReference';

View File

@@ -68,7 +68,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
// ordering
let undefinedCategoryGroup = structure.categories.get(UNDEFINED_CATEGORY)
if (undefinedCategoryGroup != null) {
totalFoods.value += undefinedCategoryGroup.foods.size
orderedStructure.push(undefinedCategoryGroup)
structure.categories.delete(UNDEFINED_CATEGORY)
}
@@ -235,6 +234,9 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
r.results.forEach((e) => {
entries.value.set(e.id!, e)
})
if(r.results.length > 0){
updateEntriesStructure()
}
currentlyUpdating.value = false
}).catch((err: any) => {
currentlyUpdating.value = false
@@ -444,6 +446,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
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) {
runSyncQueue(500)
}

View File

@@ -19,8 +19,11 @@ export function isEntryVisible(entry: ShoppingListEntry, deviceSettings: DeviceS
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.some(sl => (entry.shoppingLists?.findIndex(eSl => eSl.id == sl) != -1))){
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
}
}