diff --git a/cookbook/helper/shopping_helper.py b/cookbook/helper/shopping_helper.py index 98bfe03fb..7e1fa6c76 100644 --- a/cookbook/helper/shopping_helper.py +++ b/cookbook/helper/shopping_helper.py @@ -79,7 +79,7 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None elif ingredients: ingredients = Ingredient.objects.filter(pk__in=ingredients, space=space) else: - ingredients = Ingredient.objects.filter(step__recipe=r, space=space) + ingredients = Ingredient.objects.filter(step__recipe=r, food__ignore_shopping=False, space=space) if exclude_onhand := created_by.userpreference.mealplan_autoexclude_onhand: ingredients = ingredients.exclude(food__onhand_users__id__in=[x.id for x in shared_users]) @@ -101,9 +101,9 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None if ingredients.filter(food__recipe=x).exists(): for ing in ingredients.filter(food__recipe=x): if exclude_onhand: - x_ing = Ingredient.objects.filter(step__recipe=x, space=space).exclude(food__onhand_users__id__in=[x.id for x in shared_users]) + x_ing = Ingredient.objects.filter(step__recipe=x, food__ignore_shopping=False, space=space).exclude(food__onhand_users__id__in=[x.id for x in shared_users]) else: - x_ing = Ingredient.objects.filter(step__recipe=x, space=space) + x_ing = Ingredient.objects.filter(step__recipe=x, food__ignore_shopping=False, space=space).exclude(food__ignore_shopping=True) for i in [x for x in x_ing]: ShoppingListEntry.objects.create( list_recipe=list_recipe, diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 288f0538d..8a5212cdc 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -33,7 +33,7 @@ class ExtendedRecipeMixin(serializers.ModelSerializer): images = None image = serializers.SerializerMethodField('get_image') - numrecipe = serializers.ReadOnlyField(source='count_recipes_test') + numrecipe = serializers.ReadOnlyField(source='recipe_count') def get_fields(self, *args, **kwargs): fields = super().get_fields(*args, **kwargs) @@ -58,9 +58,6 @@ class ExtendedRecipeMixin(serializers.ModelSerializer): if obj.recipe_image: return MEDIA_URL + obj.recipe_image - def count_recipes(self, obj): - return Recipe.objects.filter(**{self.recipe_filter: obj}, space=obj.space).count() - class CustomDecimalField(serializers.Field): """ @@ -169,6 +166,11 @@ class UserPreferenceSerializer(WritableNestedModelSerializer): food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', many=True, allow_null=True, required=False, read_only=True) plan_share = UserNameSerializer(many=True, allow_null=True, required=False, read_only=True) shopping_share = UserNameSerializer(many=True, allow_null=True, required=False) + food_children_exist = serializers.SerializerMethodField('get_food_children_exist') + + def get_food_children_exist(self, obj): + space = getattr(self.context.get('request', None), 'space', None) + return Food.objects.filter(depth__gt=0, space=space).exists() def create(self, validated_data): if not validated_data.get('user', None): @@ -183,7 +185,7 @@ class UserPreferenceSerializer(WritableNestedModelSerializer): 'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'use_fractions', 'use_kj', 'search_style', 'show_recent', 'plan_share', 'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'food_inherit_default', 'default_delay', 'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', 'csv_delim', 'csv_prefix', - 'filter_to_supermarket', 'shopping_add_onhand', 'left_handed' + 'filter_to_supermarket', 'shopping_add_onhand', 'left_handed', 'food_children_exist' ) @@ -429,7 +431,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR model = Food fields = ( 'id', 'name', 'description', 'shopping', 'recipe', 'food_onhand', 'supermarket_category', - 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name' + 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping' ) read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe') @@ -683,14 +685,15 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer): value = Decimal(value) value = value.quantize(Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero return ( - obj.name - or getattr(obj.mealplan, 'title', None) - or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)]) - or obj.recipe.name - ) + f' ({value:.2g})' + obj.name + or getattr(obj.mealplan, 'title', None) + or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)]) + or obj.recipe.name + ) + f' ({value:.2g})' def update(self, instance, validated_data): - if 'servings' in validated_data: + # TODO remove once old shopping list + if 'servings' in validated_data and self.context.get('view', None).__class__.__name__ != 'ShoppingListViewSet': list_from_recipe( list_recipe=instance, servings=validated_data['servings'], diff --git a/cookbook/signals.py b/cookbook/signals.py index d763b8977..b4a528742 100644 --- a/cookbook/signals.py +++ b/cookbook/signals.py @@ -121,11 +121,3 @@ def auto_add_shopping(sender, instance=None, created=False, weak=False, **kwargs 'servings': instance.servings } list_recipe = list_from_recipe(**kwargs) - - -# user = self.context['request'].user -# if user.userpreference.shopping_add_onhand: -# if checked := validated_data.get('checked', None): -# instance.food.onhand_users.add(*user.userpreference.shopping_share.all(), user) -# elif checked == False: -# instance.food.onhand_users.remove(*user.userpreference.shopping_share.all(), user) diff --git a/cookbook/templates/shopping_list.html b/cookbook/templates/shopping_list.html index cfb2cb7fb..2720d5898 100644 --- a/cookbook/templates/shopping_list.html +++ b/cookbook/templates/shopping_list.html @@ -834,7 +834,7 @@ this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe.id)).then((response) => { for (let s of response.data.steps) { for (let i of s.ingredients) { - if (!i.is_header && i.food !== null && i.food.food_onhand === false) { + if (!i.is_header && i.food !== null && !i.food.ignore_food) { this.shopping_list.entries.push({ 'list_recipe': slr.id, 'food': i.food, diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 1d5140563..9197f53e8 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -118,7 +118,7 @@ class ExtendedRecipeMixin(): # add a recipe count annotation to the query # explanation on construction https://stackoverflow.com/a/43771738/15762829 recipe_count = Recipe.objects.filter(**{recipe_filter: OuterRef('id')}, space=space).values(recipe_filter).annotate(count=Count('pk')).values('count') - queryset = queryset.annotate(recipe_count_test=Coalesce(Subquery(recipe_count), 0)) + queryset = queryset.annotate(recipe_count=Coalesce(Subquery(recipe_count), 0)) # add a recipe image annotation to the query image_subquery = Recipe.objects.filter(**{recipe_filter: OuterRef('id')}, space=space).exclude(image__isnull=True).exclude(image__exact='').order_by("?").values('image')[:1] @@ -400,7 +400,7 @@ class SupermarketCategoryViewSet(viewsets.ModelViewSet, StandardFilterMixin): permission_classes = [CustomIsUser] def get_queryset(self): - self.queryset = self.queryset.filter(space=self.request.space) + self.queryset = self.queryset.filter(space=self.request.space).order_by('name') return super().get_queryset() diff --git a/vue/src/apps/ModelListView/ModelListView.vue b/vue/src/apps/ModelListView/ModelListView.vue index 1449acbc0..15ef44a49 100644 --- a/vue/src/apps/ModelListView/ModelListView.vue +++ b/vue/src/apps/ModelListView/ModelListView.vue @@ -18,7 +18,7 @@

- {{ this.this_model.name }} + {{ $t(this.this_model.name) }} diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index 7a66b7c4b..48bfb2590 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -1,6 +1,6 @@ diff --git a/vue/src/components/IngredientComponent.vue b/vue/src/components/IngredientComponent.vue index 595db720b..b3e9919fe 100644 --- a/vue/src/components/IngredientComponent.vue +++ b/vue/src/components/IngredientComponent.vue @@ -34,6 +34,7 @@ - + - + @@ -100,10 +101,10 @@ export default { filtered_list = filtered_list.filter((x) => x.list_recipe == this.recipe_list) } // how many ShoppingListRecipes are there for this recipe? - let count_shopping_recipes = [...new Set(filtered_list.map((x) => x.list_recipe))].length + let count_shopping_recipes = [...new Set(filtered_list.filter((x) => x.list_recipe))].length let count_shopping_ingredient = filtered_list.filter((x) => x.ingredient == this.ingredient.id).length - if (count_shopping_recipes >= 1) { + if (count_shopping_recipes >= 1 && this.recipe_list) { // This recipe is in the shopping list this.shop = false // don't check any boxes until user selects a shopping list to edit if (count_shopping_ingredient >= 1) { @@ -117,7 +118,7 @@ export default { } else { // there are not recipes in the shopping list // set default value - this.shop = !this.ingredient?.food?.food_onhand && !this.ingredient?.food?.recipe + this.shop = !this.ingredient?.food?.food_onhand && !this.ingredient?.food?.recipe && !this.ingredient?.food?.ignore_shopping this.$emit("add-to-shopping", { item: this.ingredient, add: this.shop }) // mark checked if the food is in the shopping list for this ingredient/recipe if (count_shopping_ingredient >= 1) { @@ -135,7 +136,7 @@ export default { if (this.add_shopping_mode) { // if we are in add shopping mode (e.g. recipe_shopping_modal) start with all checks marked // except if on_hand (could be if recipe too?) - this.shop = !this.ingredient?.food?.food_onhand && !this.ingredient?.food?.recipe + this.shop = !this.ingredient?.food?.food_onhand && !this.ingredient?.food?.recipe && !this.ingredient?.food?.ignore_shopping } }, }, diff --git a/vue/src/components/Modals/CheckboxInput.vue b/vue/src/components/Modals/CheckboxInput.vue index a6ee13b3c..8a7bb04b9 100644 --- a/vue/src/components/Modals/CheckboxInput.vue +++ b/vue/src/components/Modals/CheckboxInput.vue @@ -1,34 +1,34 @@ \ No newline at end of file + diff --git a/vue/src/components/Modals/GenericModalForm.vue b/vue/src/components/Modals/GenericModalForm.vue index 6a0981a65..48e429879 100644 --- a/vue/src/components/Modals/GenericModalForm.vue +++ b/vue/src/components/Modals/GenericModalForm.vue @@ -2,22 +2,26 @@

{{ f.label }}

- - - + + +
-
@@ -31,7 +35,7 @@ import { getForm, formFunctions } from "@/utils/utils" Vue.use(BootstrapVue) import { ApiApiFactory } from "@/utils/openapi/api" -import { ApiMixin, StandardToasts, ToastMixin } from "@/utils/utils" +import { ApiMixin, StandardToasts, ToastMixin, getUserPreference } from "@/utils/utils" import CheckboxInput from "@/components/Modals/CheckboxInput" import LookupInput from "@/components/Modals/LookupInput" import TextInput from "@/components/Modals/TextInput" @@ -39,10 +43,11 @@ import EmojiInput from "@/components/Modals/EmojiInput" import ChoiceInput from "@/components/Modals/ChoiceInput" import FileInput from "@/components/Modals/FileInput" import SmallText from "@/components/Modals/SmallText" +import HelpBadge from "@/components/Badges/Help" export default { name: "GenericModalForm", - components: { FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput, SmallText }, + components: { FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput, SmallText, HelpBadge }, mixins: [ApiMixin, ToastMixin], props: { model: { required: true, type: Object }, @@ -73,6 +78,7 @@ export default { form: {}, dirty: false, special_handling: false, + show_help: true, } }, mounted() { @@ -83,11 +89,19 @@ export default { buttonLabel() { return this.buttons[this.action].label }, + showHelp() { + if (this.show_help) { + return true + } else { + return undefined + } + }, }, watch: { show: function () { if (this.show) { this.form = getForm(this.model, this.action, this.item1, this.item2) + if (this.form?.form_function) { this.form = formFunctions[this.form.form_function](this.form) } @@ -256,15 +270,33 @@ export default { let type_match = field?.type == field_type let checks = true if (type_match && field?.condition) { - if (field.condition?.condition === "exists") { - if ((this.item1[field.condition.field] != undefined) === field.condition.value) { - checks = true - } else { - checks = false - } + const value = this.item1[field?.condition?.field] + const preference = getUserPreference(field?.condition?.field) + console.log("condition", field?.condition?.condition) + switch (field?.condition?.condition) { + case "field_exists": + if ((value != undefined) === field.condition.value) { + checks = true + } else { + checks = false + } + break + case "preference__array_exists": + if (preference?.length > 0 === field.condition.value) { + checks = true + } else { + checks = false + } + break + case "preference_equals": + if (preference === field.condition.value) { + checks = true + } else { + checks = false + } + break } } - return type_match && checks }, }, diff --git a/vue/src/components/Modals/LookupInput.vue b/vue/src/components/Modals/LookupInput.vue index 7f41954ce..fa92f9562 100644 --- a/vue/src/components/Modals/LookupInput.vue +++ b/vue/src/components/Modals/LookupInput.vue @@ -19,6 +19,7 @@ @new="addNew" > + {{ help }} @@ -47,6 +48,7 @@ export default { class_list: { type: String, default: "mb-3" }, show_label: { type: Boolean, default: true }, clear: { type: Number }, + help: { type: String, default: undefined }, }, data() { return { diff --git a/vue/src/components/Modals/TextInput.vue b/vue/src/components/Modals/TextInput.vue index 94d5225b8..bd25ebc2d 100644 --- a/vue/src/components/Modals/TextInput.vue +++ b/vue/src/components/Modals/TextInput.vue @@ -2,6 +2,8 @@
+ {{ help }} + {{ subtitle }}
@@ -14,7 +16,8 @@ export default { label: { type: String, default: "Text Field" }, value: { type: String, default: "" }, placeholder: { type: String, default: "You Should Add Placeholder Text" }, - show_merge: { type: Boolean, default: false }, + help: { type: String, default: undefined }, + subtitle: { type: String, default: undefined }, }, data() { return { diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index f289a5b32..6758ab6a9 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -289,5 +289,10 @@ "remember_hours": "Hours to Remember", "tree_select": "Use Tree Selection", "left_handed": "Left-handed mode", - "left_handed_help": "Will optimize the UI for use with your left hand." + "left_handed_help": "Will optimize the UI for use with your left hand.", + "OnHand_help": "Food is in inventory and will not be automatically added to a shopping list.", + "ignore_shopping_help": "Never add food to the shopping list (e.g. water)", + "shopping_category_help": "Supermarkets can be ordered and filtered by Shopping Category according to the layout of the aisles.", + "food_recipe_help": "Linking a recipe here will include the linked recipe in any other recipe that use this food", + "Foods":"Foods" } diff --git a/vue/src/utils/models.js b/vue/src/utils/models.js index 209f10eae..01a8e720d 100644 --- a/vue/src/utils/models.js +++ b/vue/src/utils/models.js @@ -59,7 +59,7 @@ export class Models { // MODELS - inherits and takes precedence over MODEL_TYPES and ACTIONS static FOOD = { - name: i18n.t("Food"), // *OPTIONAL* : parameters will be built model -> model_type -> default + name: "Food", // *OPTIONAL* : parameters will be built model -> model_type -> default apiName: "Food", // *REQUIRED* : the name that is used in api.ts for this model model_type: this.TREE, // *OPTIONAL* : model specific params for api, if not present will attempt modeltype_create then default_create paginated: true, @@ -76,15 +76,17 @@ export class Models { // REQUIRED: unordered array of fields that can be set during create create: { // if not defined partialUpdate will use the same parameters, prepending 'id' - params: [["name", "description", "recipe", "food_onhand", "supermarket_category", "inherit", "inherit_fields"]], + params: [["name", "description", "recipe", "food_onhand", "supermarket_category", "inherit", "inherit_fields", "ignore_shopping"]], form: { + show_help: true, name: { form_field: true, type: "text", field: "name", label: i18n.t("Name"), placeholder: "", + subtitle_field: "full_name", }, description: { form_field: true, @@ -99,12 +101,21 @@ export class Models { field: "recipe", list: "RECIPE", label: i18n.t("Recipe"), + help_text: i18n.t("food_recipe_help"), }, - shopping: { + onhand: { form_field: true, type: "checkbox", field: "food_onhand", label: i18n.t("OnHand"), + help_text: i18n.t("OnHand_help"), + }, + ignore_shopping: { + form_field: true, + type: "checkbox", + field: "ignore_shopping", + label: i18n.t("Ignore_Shopping"), + help_text: i18n.t("ignore_shopping_help"), }, shopping_category: { form_field: true, @@ -113,6 +124,7 @@ export class Models { list: "SHOPPING_CATEGORY", label: i18n.t("Shopping_Category"), allow_create: true, + help_text: i18n.t("shopping_category_help"), }, inherit_fields: { form_field: true, @@ -121,12 +133,7 @@ export class Models { field: "inherit_fields", list: "FOOD_INHERIT_FIELDS", label: i18n.t("InheritFields"), - condition: { field: "parent", value: true, condition: "exists" }, - }, - full_name: { - form_field: true, - type: "smalltext", - field: "full_name", + condition: { field: "food_children_exist", value: true, condition: "preference_equals" }, }, form_function: "FoodCreateDefault", }, @@ -136,12 +143,12 @@ export class Models { }, } static FOOD_INHERIT_FIELDS = { - name: i18n.t("FoodInherit"), + name: "FoodInherit", apiName: "FoodInheritField", } static KEYWORD = { - name: i18n.t("Keyword"), // *OPTIONAL: parameters will be built model -> model_type -> default + name: "Keyword", // *OPTIONAL: parameters will be built model -> model_type -> default apiName: "Keyword", model_type: this.TREE, paginated: true, @@ -184,7 +191,7 @@ export class Models { } static UNIT = { - name: i18n.t("Unit"), + name: "Unit", apiName: "Unit", paginated: true, create: { @@ -210,7 +217,7 @@ export class Models { } static SHOPPING_LIST = { - name: i18n.t("Shopping_list"), + name: "Shopping_list", apiName: "ShoppingListEntry", list: { params: ["id", "checked", "supermarket", "options"], @@ -239,7 +246,7 @@ export class Models { } static RECIPE_BOOK = { - name: i18n.t("Recipe_Book"), + name: "Recipe_Book", apiName: "RecipeBook", create: { params: [["name", "description", "icon"]], @@ -269,7 +276,7 @@ export class Models { } static SHOPPING_CATEGORY = { - name: i18n.t("Shopping_Category"), + name: "Shopping_Category", apiName: "SupermarketCategory", create: { params: [["name", "description"]], @@ -293,7 +300,7 @@ export class Models { } static SHOPPING_CATEGORY_RELATION = { - name: i18n.t("Shopping_Category_Relation"), + name: "Shopping_Category_Relation", apiName: "SupermarketCategoryRelation", create: { params: [["category", "supermarket", "order"]], @@ -317,7 +324,7 @@ export class Models { } static SUPERMARKET = { - name: i18n.t("Supermarket"), + name: "Supermarket", apiName: "Supermarket", ordered_tags: [{ field: "category_to_supermarket", label: "category::name", color: "info" }], create: { @@ -360,7 +367,7 @@ export class Models { } static AUTOMATION = { - name: i18n.t("Automation"), + name: "Automation", apiName: "Automation", paginated: true, list: { @@ -423,7 +430,7 @@ export class Models { } static RECIPE = { - name: i18n.t("Recipe"), + name: "Recipe", apiName: "Recipe", list: { params: ["query", "keywords", "foods", "units", "rating", "books", "keywordsOr", "foodsOr", "booksOr", "internal", "random", "_new", "page", "pageSize", "options"], @@ -439,7 +446,7 @@ export class Models { } static USER_NAME = { - name: i18n.t("User"), + name: "User", apiName: "User", list: { params: ["filter_list"], @@ -447,7 +454,7 @@ export class Models { } static MEAL_TYPE = { - name: i18n.t("Meal_Type"), + name: "Meal_Type", apiName: "MealType", list: { params: ["filter_list"], @@ -455,7 +462,7 @@ export class Models { } static MEAL_PLAN = { - name: i18n.t("Meal_Plan"), + name: "Meal_Plan", apiName: "MealPlan", list: { params: ["options"], @@ -463,7 +470,7 @@ export class Models { } static USERFILE = { - name: i18n.t("File"), + name: "File", apiName: "UserFile", paginated: false, list: { @@ -492,13 +499,13 @@ export class Models { }, } static USER = { - name: i18n.t("User"), + name: "User", apiName: "User", paginated: false, } static STEP = { - name: i18n.t("Step"), + name: "Step", apiName: "Step", list: { params: ["recipe", "query", "page", "pageSize", "options"], diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts index 8a8c1cfff..43b5182c0 100644 --- a/vue/src/utils/openapi/api.ts +++ b/vue/src/utils/openapi/api.ts @@ -211,10 +211,10 @@ export interface Food { recipe?: FoodRecipe | null; /** * - * @type {boolean} + * @type {string} * @memberof Food */ - food_onhand?: boolean; + food_onhand?: string | null; /** * * @type {FoodSupermarketCategory} @@ -245,6 +245,12 @@ export interface Food { * @memberof Food */ full_name?: string; + /** + * + * @type {boolean} + * @memberof Food + */ + ignore_shopping?: boolean; } /** * @@ -607,10 +613,10 @@ export interface IngredientFood { recipe?: FoodRecipe | null; /** * - * @type {boolean} + * @type {string} * @memberof IngredientFood */ - food_onhand?: boolean; + food_onhand?: string | null; /** * * @type {FoodSupermarketCategory} @@ -641,6 +647,12 @@ export interface IngredientFood { * @memberof IngredientFood */ full_name?: string; + /** + * + * @type {boolean} + * @memberof IngredientFood + */ + ignore_shopping?: boolean; } /** * @@ -1182,7 +1194,7 @@ export interface MealPlanRecipe { * @type {any} * @memberof MealPlanRecipe */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1255,6 +1267,12 @@ export interface MealPlanRecipe { * @memberof MealPlanRecipe */ _new?: string; + /** + * + * @type {string} + * @memberof MealPlanRecipe + */ + recent?: string; } /** * @@ -1372,7 +1390,7 @@ export interface Recipe { * @type {any} * @memberof Recipe */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1715,25 +1733,25 @@ export interface RecipeNutrition { * @type {string} * @memberof RecipeNutrition */ - carbohydrates?: string; + carbohydrates: string; /** * * @type {string} * @memberof RecipeNutrition */ - fats?: string; + fats: string; /** * * @type {string} * @memberof RecipeNutrition */ - proteins?: string; + proteins: string; /** * * @type {string} * @memberof RecipeNutrition */ - calories?: string; + calories: string; /** * * @type {string} @@ -1770,7 +1788,7 @@ export interface RecipeOverview { * @type {any} * @memberof RecipeOverview */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1843,6 +1861,12 @@ export interface RecipeOverview { * @memberof RecipeOverview */ _new?: string; + /** + * + * @type {string} + * @memberof RecipeOverview + */ + recent?: string; } /** * @@ -1918,12 +1942,6 @@ export interface RecipeSteps { * @memberof RecipeSteps */ name?: string; - /** - * - * @type {string} - * @memberof RecipeSteps - */ - type?: RecipeStepsTypeEnum; /** * * @type {string} @@ -1991,18 +2009,6 @@ export interface RecipeSteps { */ numrecipe?: string; } - -/** - * @export - * @enum {string} - */ -export enum RecipeStepsTypeEnum { - Text = 'TEXT', - Time = 'TIME', - File = 'FILE', - Recipe = 'RECIPE' -} - /** * * @export @@ -2523,12 +2529,6 @@ export interface Step { * @memberof Step */ name?: string; - /** - * - * @type {string} - * @memberof Step - */ - type?: StepTypeEnum; /** * * @type {string} @@ -2596,18 +2596,6 @@ export interface Step { */ numrecipe?: string; } - -/** - * @export - * @enum {string} - */ -export enum StepTypeEnum { - Text = 'TEXT', - Time = 'TIME', - File = 'FILE', - Recipe = 'RECIPE' -} - /** * * @export @@ -2952,6 +2940,12 @@ export interface UserPreference { * @memberof UserPreference */ default_page?: UserPreferenceDefaultPageEnum; + /** + * + * @type {boolean} + * @memberof UserPreference + */ + use_fractions?: boolean; /** * * @type {boolean} @@ -3026,10 +3020,10 @@ export interface UserPreference { mealplan_autoexclude_onhand?: boolean; /** * - * @type {Array} + * @type {Array} * @memberof UserPreference */ - shopping_share?: Array; + shopping_share?: Array | null; /** * * @type {number} @@ -3054,6 +3048,18 @@ export interface UserPreference { * @memberof UserPreference */ filter_to_supermarket?: boolean; + /** + * + * @type {boolean} + * @memberof UserPreference + */ + shopping_add_onhand?: boolean; + /** + * + * @type {boolean} + * @memberof UserPreference + */ + left_handed?: boolean; } /** diff --git a/vue/src/utils/utils.js b/vue/src/utils/utils.js index 5d332ee59..f03024ded 100644 --- a/vue/src/utils/utils.js +++ b/vue/src/utils/utils.js @@ -156,7 +156,7 @@ export function getUserPreference(pref = undefined) { return undefined } if (pref) { - return user_preference[pref] + return user_preference?.[pref] } return user_preference } @@ -389,6 +389,8 @@ export function getForm(model, action, item1, item2) { } if (value?.form_field) { value["value"] = item1?.[value?.field] ?? undefined + value["help"] = item1?.[value?.help_text_field] ?? value?.help_text ?? undefined + value["subtitle"] = item1?.[value?.subtitle_field] ?? value?.subtitle ?? undefined form.fields.push({ ...value, ...{